@babylonjs/inspector 8.52.0 → 8.53.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,7 @@
1
1
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
2
2
  import { createContext, forwardRef, useContext, useState, useCallback, Component, useMemo, useEffect, useRef, useReducer, Children, isValidElement, useLayoutEffect, cloneElement, useImperativeHandle, createElement, Suspense, memo, Fragment as Fragment$1, lazy } from 'react';
3
- import { tokens, makeStyles, Tooltip as Tooltip$1, Button as Button$1, Spinner, Link as Link$1, Caption1, Body1, useFluent, Accordion as Accordion$1, AccordionHeader, Subtitle2Stronger, AccordionPanel, Divider, MessageBar as MessageBar$1, MessageBarBody, AccordionItem, SearchBox as SearchBox$1, Portal, ToggleButton as ToggleButton$1, InfoLabel as InfoLabel$1, mergeClasses, Body1Strong, useId, useToastController, Toast, ToastTitle, FluentProvider, Toaster, Checkbox as Checkbox$1, createLightTheme, createDarkTheme, TeachingPopover, TeachingPopoverSurface, TeachingPopoverHeader, TeachingPopoverBody, Switch as Switch$1, createDOMRenderer, RendererProvider, Menu, MenuTrigger, SplitButton, MenuPopover, MenuList, MenuItem, Toolbar as Toolbar$1, ToolbarRadioButton, MenuGroup, MenuGroupHeader, treeItemLevelToken, FlatTree, FlatTreeItem, TreeItemLayout, MenuDivider, MenuItemCheckbox, SpinButton as SpinButton$1, Input, Dropdown as Dropdown$1, Option, Popover as Popover$1, PopoverTrigger, PopoverSurface, ColorPicker, ColorArea, ColorSlider, AlphaSlider, ColorSwatch, PresenceBadge, Slider, MenuItemRadio, Dialog, DialogSurface, DialogBody, DialogTitle, DialogContent, DialogActions, List as List$1, ListItem, Badge, Label, MessageBarTitle, Subtitle2, useComboboxFilter, Combobox, Textarea as Textarea$1, ToolbarButton, ToolbarDivider, Field } from '@fluentui/react-components';
4
- import { ErrorCircleRegular, EyeFilled, EyeOffRegular, CheckmarkFilled, EditRegular, FilterRegular, PinFilled, PinRegular, ArrowCircleUpRegular, ChevronCircleRight16Regular, ChevronCircleRight20Regular, ChevronCircleDown16Regular, ChevronCircleDown20Regular, Copy16Regular, CopyRegular, PanelLeftExpandRegular, PanelRightExpandRegular, PanelLeftContractRegular, PanelRightContractRegular, PictureInPictureEnterRegular, MoreHorizontalRegular, LayoutColumnTwoFocusLeftFilled, LayoutColumnTwoSplitLeftFocusTopLeftFilled, LayoutColumnTwoSplitLeftFocusBottomLeftFilled, LayoutColumnTwoFocusRightFilled, LayoutColumnTwoSplitRightFocusTopRightFilled, LayoutColumnTwoSplitRightFocusBottomRightFilled, SettingsRegular, DocumentTextRegular, createFluentIcon, TextSortAscendingRegular, GlobeRegular, ArrowExpandAllRegular, ArrowCollapseAllRegular, CubeTreeRegular, BugRegular, ArrowUploadRegular, ArrowDownloadRegular, StopRegular, RecordRegular, DataBarHorizontalRegular, WrenchRegular, WeatherSunnyRegular, WeatherMoonRegular, ArrowRotateClockwiseRegular, ArrowExpandRegular, SelectObjectRegular, CubeRegular, CameraRegular, AddRegular, DeleteRegular, FullScreenMaximizeRegular, ChevronDownRegular, ChevronRightRegular, CircleSmallFilled, SaveRegular, PreviousRegular, ArrowPreviousRegular, TriangleLeftRegular, RecordStopRegular, PlayRegular, ArrowNextRegular, NextRegular, PauseRegular, LinkDismissRegular, LinkEditRegular, ArrowUndoRegular, BracesRegular, BracesDismiss16Regular, EyeRegular, CloudArrowUpRegular, CloudArrowDownRegular, EyeOffFilled, ArrowMoveFilled, StopFilled, PlayFilled, LockOpenRegular, LockClosedRegular, ResizeRegular, ChevronUpRegular, ArrowResetRegular, CircleHalfFillRegular, EyedropperRegular, PaintBucketRegular, InkStrokeRegular, StackRegular, FilmstripRegular, PauseFilled, WeatherSunnyLowFilled, LayerRegular, FrameRegular, AppGenericRegular, RectangleLandscapeRegular, BorderOutsideRegular, BorderNoneRegular, MyLocationRegular, LightbulbRegular, VideoFilled, VideoRegular, FlashlightRegular, FlashlightOffRegular, DropRegular, BlurRegular, PipelineRegular, PersonWalkingRegular, DataLineRegular, SoundWaveCircleRegular, PersonSquareRegular, LayerDiagonalPersonRegular, ImageEditRegular, ImageRegular, TargetRegular, PersonFeedbackRegular, BranchRegular, DeleteFilled } from '@fluentui/react-icons';
3
+ import { tokens, makeStyles, Tooltip as Tooltip$1, Button as Button$1, Spinner, Link as Link$1, Caption1, Body1, useFluent, Accordion as Accordion$1, AccordionHeader, Subtitle2Stronger, AccordionPanel, Divider, MessageBar as MessageBar$1, MessageBarBody, AccordionItem, SearchBox as SearchBox$1, Portal, ToggleButton as ToggleButton$1, InfoLabel as InfoLabel$1, mergeClasses, Body1Strong, useId, useToastController, Toast, ToastTitle, FluentProvider, Toaster, Checkbox as Checkbox$1, createLightTheme, createDarkTheme, TeachingPopover, TeachingPopoverSurface, TeachingPopoverHeader, TeachingPopoverBody, Switch as Switch$1, createDOMRenderer, RendererProvider, Menu, MenuTrigger, SplitButton, MenuPopover, MenuList, MenuItem, Toolbar as Toolbar$1, ToolbarRadioButton, MenuGroup, MenuGroupHeader, treeItemLevelToken, FlatTree, FlatTreeItem, TreeItemLayout, MenuDivider, MenuItemCheckbox, useMergedRefs, Input, Dropdown as Dropdown$1, Option, Popover as Popover$1, PopoverTrigger, PopoverSurface, ColorPicker, ColorArea, ColorSlider, AlphaSlider, ColorSwatch, PresenceBadge, Slider as Slider$1, MenuItemRadio, Dialog, DialogSurface, DialogBody, DialogTitle, DialogContent, DialogActions, List as List$1, ListItem, Badge, Label, MessageBarTitle, Subtitle2, useComboboxFilter, Combobox, Textarea as Textarea$1, ToolbarButton, ToolbarDivider, Field } from '@fluentui/react-components';
4
+ import { ErrorCircleRegular, EyeFilled, EyeOffRegular, CheckmarkFilled, EditRegular, FilterRegular, PinFilled, PinRegular, ArrowCircleUpRegular, ChevronCircleRight16Regular, ChevronCircleRight20Regular, ChevronCircleDown16Regular, ChevronCircleDown20Regular, Copy16Regular, CopyRegular, PanelLeftExpandRegular, PanelRightExpandRegular, PanelLeftContractRegular, PanelRightContractRegular, PictureInPictureEnterRegular, MoreHorizontalRegular, LayoutColumnTwoFocusLeftFilled, LayoutColumnTwoSplitLeftFocusTopLeftFilled, LayoutColumnTwoSplitLeftFocusBottomLeftFilled, LayoutColumnTwoFocusRightFilled, LayoutColumnTwoSplitRightFocusTopRightFilled, LayoutColumnTwoSplitRightFocusBottomRightFilled, SettingsRegular, DocumentTextRegular, createFluentIcon, TextSortAscendingRegular, GlobeRegular, ArrowExpandAllRegular, ArrowCollapseAllRegular, CubeTreeRegular, BugRegular, ArrowUploadRegular, ArrowBidirectionalUpDownFilled, ArrowDownloadRegular, StopRegular, RecordRegular, DataBarHorizontalRegular, WrenchRegular, ArrowClockwiseRegular, WeatherSunnyRegular, WeatherMoonRegular, ArrowRotateClockwiseRegular, ArrowExpandRegular, SelectObjectRegular, CubeRegular, CameraRegular, AddRegular, DeleteRegular, FullScreenMaximizeRegular, ChevronDownRegular, ChevronRightRegular, CircleSmallFilled, SaveRegular, PreviousRegular, ArrowPreviousRegular, TriangleLeftRegular, RecordStopRegular, PlayRegular, ArrowNextRegular, NextRegular, PauseRegular, LinkDismissRegular, LinkEditRegular, ArrowUndoRegular, BracesRegular, BracesDismiss16Regular, EyeRegular, CloudArrowUpRegular, CloudArrowDownRegular, EyeOffFilled, ArrowMoveFilled, StopFilled, PlayFilled, LockOpenRegular, LockClosedRegular, ResizeRegular, ChevronUpRegular, ArrowResetRegular, CircleHalfFillRegular, EyedropperRegular, PaintBucketRegular, InkStrokeRegular, StackRegular, FilmstripRegular, PauseFilled, WeatherSunnyLowFilled, LayerRegular, FrameRegular, AppGenericRegular, RectangleLandscapeRegular, BorderOutsideRegular, BorderNoneRegular, MyLocationRegular, LightbulbRegular, VideoFilled, VideoRegular, FlashlightRegular, FlashlightOffRegular, DropRegular, BlurRegular, PipelineRegular, PersonWalkingRegular, DataLineRegular, SoundWaveCircleRegular, PersonSquareRegular, LayerDiagonalPersonRegular, ImageEditRegular, ImageRegular, TargetRegular, PersonFeedbackRegular, BranchRegular, DeleteFilled } from '@fluentui/react-icons';
5
5
  import { Color3, Color4 } from '@babylonjs/core/Maths/math.color.js';
6
6
  import { Vector3, Quaternion, Matrix, Vector2, Vector4, TmpVectors } from '@babylonjs/core/Maths/math.vector.js';
7
7
  import { Observable } from '@babylonjs/core/Misc/observable.js';
@@ -37,7 +37,7 @@ import { PerformanceViewerCollector } from '@babylonjs/core/Misc/PerformanceView
37
37
  import { AbstractEngine } from '@babylonjs/core/Engines/abstractEngine.js';
38
38
  import { Bone } from '@babylonjs/core/Bones/bone.js';
39
39
  import { Camera } from '@babylonjs/core/Cameras/camera.js';
40
- import { FrameGraphUtils } from '@babylonjs/core/FrameGraph/frameGraphUtils.js';
40
+ import { FrameGraphUtils, FindMainCamera, FindMainObjectRenderer } from '@babylonjs/core/FrameGraph/frameGraphUtils.js';
41
41
  import { CameraGizmo } from '@babylonjs/core/Gizmos/cameraGizmo.js';
42
42
  import { GizmoManager } from '@babylonjs/core/Gizmos/gizmoManager.js';
43
43
  import { LightGizmo } from '@babylonjs/core/Gizmos/lightGizmo.js';
@@ -45,6 +45,8 @@ import { Light } from '@babylonjs/core/Lights/light.js';
45
45
  import { AbstractMesh } from '@babylonjs/core/Meshes/abstractMesh.js';
46
46
  import { Node as Node$1 } from '@babylonjs/core/node.js';
47
47
  import { createRoot } from 'react-dom/client';
48
+ import { SelectionOutlineLayer } from '@babylonjs/core/Layers/selectionOutlineLayer.js';
49
+ import { GaussianSplattingMesh } from '@babylonjs/core/Meshes/GaussianSplatting/gaussianSplattingMesh.js';
48
50
  import { AnimationGroup, TargetedAnimation } from '@babylonjs/core/Animations/animationGroup.js';
49
51
  import { Animation } from '@babylonjs/core/Animations/animation.js';
50
52
  import { AnimationPropertiesOverride } from '@babylonjs/core/Animations/animationPropertiesOverride.js';
@@ -80,7 +82,6 @@ import { NodeMaterialBlockConnectionPointTypes } from '@babylonjs/core/Materials
80
82
  import { FactorGradient, Color3Gradient, ColorGradient } from '@babylonjs/core/Misc/gradients.js';
81
83
  import { ReadFile } from '@babylonjs/core/Misc/fileTools.js';
82
84
  import { CubeTexture } from '@babylonjs/core/Materials/Textures/cubeTexture.js';
83
- import { GaussianSplattingMesh } from '@babylonjs/core/Meshes/GaussianSplatting/gaussianSplattingMesh.js';
84
85
  import { Mesh } from '@babylonjs/core/Meshes/mesh.js';
85
86
  import { SkeletonViewer } from '@babylonjs/core/Debug/skeletonViewer.js';
86
87
  import { VertexBuffer } from '@babylonjs/core/Meshes/buffer.js';
@@ -144,7 +145,6 @@ import { ImportAnimationsAsync, SceneLoader } from '@babylonjs/core/Loading/scen
144
145
  import { FilesInput } from '@babylonjs/core/Misc/filesInput.js';
145
146
  import { GLTFLoaderAnimationStartMode, GLTFLoaderCoordinateSystemMode, GLTFLoaderDefaultOptions } from '@babylonjs/loaders/glTF/glTFFileLoader.js';
146
147
  import { GLTFValidation } from '@babylonjs/loaders/glTF/glTFValidation.js';
147
- import { SelectionOutlineLayer } from '@babylonjs/core/Layers/selectionOutlineLayer.js';
148
148
  import { EngineStore } from '@babylonjs/core/Engines/engineStore.js';
149
149
  import { DebugLayer } from '@babylonjs/core/Debug/debugLayer.js';
150
150
  import { Lazy } from '@babylonjs/core/Misc/lazy.js';
@@ -276,7 +276,7 @@ const Button = forwardRef((props, ref) => {
276
276
  });
277
277
  Button.displayName = "Button";
278
278
 
279
- const useStyles$T = makeStyles({
279
+ const useStyles$U = makeStyles({
280
280
  root: {
281
281
  display: "flex",
282
282
  flexDirection: "column",
@@ -357,7 +357,7 @@ class ErrorBoundary extends Component {
357
357
  }
358
358
  }
359
359
  function ErrorFallback({ error, onRetry }) {
360
- const styles = useStyles$T();
360
+ const styles = useStyles$U();
361
361
  return (jsxs("div", { className: styles.root, children: [jsx(ErrorCircleRegular, { className: styles.icon }), jsx("div", { className: styles.title, children: "Something went wrong" }), jsx("div", { className: styles.message, children: "An error occurred in this component. You can try again or continue using other parts of the tool." }), jsx(Button, { label: "Try Again", appearance: "primary", onClick: onRetry }), error && jsx("div", { className: styles.details, children: error.message })] }));
362
362
  }
363
363
 
@@ -369,82 +369,6 @@ function usePropertyChangedNotifier() {
369
369
  }, [propertyContext]);
370
370
  }
371
371
 
372
- const InterceptorHooksMaps$1 = new WeakMap();
373
- /** @internal */
374
- function InterceptFunction(target, propertyKey, hooks) {
375
- if (!hooks.afterCall) {
376
- throw new Error("At least one hook must be provided.");
377
- }
378
- const originalFunction = Reflect.get(target, propertyKey, target);
379
- if (typeof originalFunction !== "function") {
380
- throw new Error(`Property "${propertyKey.toString()}" of object "${target}" is not a function.`);
381
- }
382
- // Make sure the property is configurable and writable, otherwise it is immutable and cannot be intercepted.
383
- const propertyDescriptor = Reflect.getOwnPropertyDescriptor(target, propertyKey);
384
- if (propertyDescriptor) {
385
- if (!propertyDescriptor.configurable) {
386
- throw new Error(`Property "${propertyKey.toString()}" of object "${target}" is not configurable.`);
387
- }
388
- if (propertyDescriptor.writable === false || (propertyDescriptor.writable === undefined && !propertyDescriptor.set)) {
389
- throw new Error(`Property "${propertyKey.toString()}" of object "${target}" is readonly.`);
390
- }
391
- }
392
- // Get or create the hooks map for the target object.
393
- let hooksMap = InterceptorHooksMaps$1.get(target);
394
- if (!hooksMap) {
395
- InterceptorHooksMaps$1.set(target, (hooksMap = new Map()));
396
- }
397
- // Get or create the hooks array for the property key.
398
- let hooksForKey = hooksMap.get(propertyKey);
399
- if (!hooksForKey) {
400
- hooksMap.set(propertyKey, (hooksForKey = []));
401
- if (
402
- // Replace the function with a new one that calls the hooks in addition to the original function.
403
- !Reflect.set(target, propertyKey, (...args) => {
404
- const result = Reflect.apply(originalFunction, target, args);
405
- for (const { afterCall } of hooksForKey) {
406
- afterCall?.(...args);
407
- }
408
- return result;
409
- })) {
410
- throw new Error(`Failed to define new function "${propertyKey.toString()}" on object "${target}".`);
411
- }
412
- }
413
- hooksForKey.push(hooks);
414
- let isDisposed = false;
415
- return {
416
- dispose: () => {
417
- if (!isDisposed) {
418
- // Remove the hooks from the hooks array for the property key.
419
- hooksForKey.splice(hooksForKey.indexOf(hooks), 1);
420
- // If there are no more hooks for the property key, remove the property from the hooks map.
421
- if (hooksForKey.length === 0) {
422
- hooksMap.delete(propertyKey);
423
- // If there are no more hooks for the target object, remove the hooks map from the WeakMap.
424
- if (hooksMap.size === 0) {
425
- InterceptorHooksMaps$1.delete(target);
426
- }
427
- if (propertyDescriptor) {
428
- // If we have a property descriptor, it means the property was defined directly on the target object,
429
- // in which case we replaced it and the original property descriptor needs to be restored.
430
- if (!Reflect.defineProperty(target, propertyKey, propertyDescriptor)) {
431
- throw new Error(`Failed to restore original function "${propertyKey.toString()}" on object "${target}".`);
432
- }
433
- }
434
- else {
435
- // Otherwise, the property was inherited through the prototype chain, and so we can simply delete it from
436
- // the target object to allow it to fall back to the prototype chain as it did originally.
437
- if (!Reflect.deleteProperty(target, propertyKey)) {
438
- throw new Error(`Failed to delete transient function "${propertyKey.toString()}" on object "${target}".`);
439
- }
440
- }
441
- }
442
- isDisposed = true;
443
- }
444
- },
445
- };
446
- }
447
-
448
372
  /**
449
373
  * Gets the property descriptor for a property on an object, including inherited properties.
450
374
  * @param target The object containing the property.
@@ -474,7 +398,7 @@ function IsPropertyReadonly(propertyDescriptor) {
474
398
  // If the property is not writable, it is readonly.
475
399
  return propertyDescriptor.writable === false || (propertyDescriptor.writable === undefined && !propertyDescriptor.set);
476
400
  }
477
- const InterceptorHooksMaps = new WeakMap();
401
+ const InterceptorHooksMaps$1 = new WeakMap();
478
402
  /** @internal */
479
403
  function InterceptProperty(target, propertyKey, hooks) {
480
404
  // Find the property descriptor and note the owning object (might be inherited through the prototype chain).
@@ -505,9 +429,9 @@ function InterceptProperty(target, propertyKey, hooks) {
505
429
  }
506
430
  }
507
431
  // Get or create the hooks map for the target object.
508
- let hooksMap = InterceptorHooksMaps.get(target);
432
+ let hooksMap = InterceptorHooksMaps$1.get(target);
509
433
  if (!hooksMap) {
510
- InterceptorHooksMaps.set(target, (hooksMap = new Map()));
434
+ InterceptorHooksMaps$1.set(target, (hooksMap = new Map()));
511
435
  }
512
436
  // Get or create the hooks array for the property key.
513
437
  let hooksForKey = hooksMap.get(propertyKey);
@@ -547,7 +471,7 @@ function InterceptProperty(target, propertyKey, hooks) {
547
471
  hooksMap.delete(propertyKey);
548
472
  // If there are no more hooks for the target object, remove the hooks map from the WeakMap.
549
473
  if (hooksMap.size === 0) {
550
- InterceptorHooksMaps.delete(target);
474
+ InterceptorHooksMaps$1.delete(target);
551
475
  }
552
476
  const shouldRestorePropertyDescriptor =
553
477
  // If the property is owned by the target object, then we may have replaced an original property descriptor that needs to be restore.
@@ -573,6 +497,95 @@ function InterceptProperty(target, propertyKey, hooks) {
573
497
  };
574
498
  }
575
499
 
500
+ const DefaultWatcher = {
501
+ watchProperty(target, propertyKey, onChanged) {
502
+ return InterceptProperty(target, propertyKey, {
503
+ afterSet: (value) => onChanged(value),
504
+ });
505
+ },
506
+ refresh: () => { },
507
+ };
508
+ const WatcherContext = createContext(DefaultWatcher);
509
+ function useWatcher() {
510
+ return useContext(WatcherContext);
511
+ }
512
+
513
+ const InterceptorHooksMaps = new WeakMap();
514
+ /** @internal */
515
+ function InterceptFunction(target, propertyKey, hooks) {
516
+ if (!hooks.afterCall) {
517
+ throw new Error("At least one hook must be provided.");
518
+ }
519
+ const originalFunction = Reflect.get(target, propertyKey, target);
520
+ if (typeof originalFunction !== "function") {
521
+ throw new Error(`Property "${propertyKey.toString()}" of object "${target}" is not a function.`);
522
+ }
523
+ // Make sure the property is configurable and writable, otherwise it is immutable and cannot be intercepted.
524
+ const propertyDescriptor = Reflect.getOwnPropertyDescriptor(target, propertyKey);
525
+ if (propertyDescriptor) {
526
+ if (!propertyDescriptor.configurable) {
527
+ throw new Error(`Property "${propertyKey.toString()}" of object "${target}" is not configurable.`);
528
+ }
529
+ if (propertyDescriptor.writable === false || (propertyDescriptor.writable === undefined && !propertyDescriptor.set)) {
530
+ throw new Error(`Property "${propertyKey.toString()}" of object "${target}" is readonly.`);
531
+ }
532
+ }
533
+ // Get or create the hooks map for the target object.
534
+ let hooksMap = InterceptorHooksMaps.get(target);
535
+ if (!hooksMap) {
536
+ InterceptorHooksMaps.set(target, (hooksMap = new Map()));
537
+ }
538
+ // Get or create the hooks array for the property key.
539
+ let hooksForKey = hooksMap.get(propertyKey);
540
+ if (!hooksForKey) {
541
+ hooksMap.set(propertyKey, (hooksForKey = []));
542
+ if (
543
+ // Replace the function with a new one that calls the hooks in addition to the original function.
544
+ !Reflect.set(target, propertyKey, (...args) => {
545
+ const result = Reflect.apply(originalFunction, target, args);
546
+ for (const { afterCall } of hooksForKey) {
547
+ afterCall?.(...args);
548
+ }
549
+ return result;
550
+ })) {
551
+ throw new Error(`Failed to define new function "${propertyKey.toString()}" on object "${target}".`);
552
+ }
553
+ }
554
+ hooksForKey.push(hooks);
555
+ let isDisposed = false;
556
+ return {
557
+ dispose: () => {
558
+ if (!isDisposed) {
559
+ // Remove the hooks from the hooks array for the property key.
560
+ hooksForKey.splice(hooksForKey.indexOf(hooks), 1);
561
+ // If there are no more hooks for the property key, remove the property from the hooks map.
562
+ if (hooksForKey.length === 0) {
563
+ hooksMap.delete(propertyKey);
564
+ // If there are no more hooks for the target object, remove the hooks map from the WeakMap.
565
+ if (hooksMap.size === 0) {
566
+ InterceptorHooksMaps.delete(target);
567
+ }
568
+ if (propertyDescriptor) {
569
+ // If we have a property descriptor, it means the property was defined directly on the target object,
570
+ // in which case we replaced it and the original property descriptor needs to be restored.
571
+ if (!Reflect.defineProperty(target, propertyKey, propertyDescriptor)) {
572
+ throw new Error(`Failed to restore original function "${propertyKey.toString()}" on object "${target}".`);
573
+ }
574
+ }
575
+ else {
576
+ // Otherwise, the property was inherited through the prototype chain, and so we can simply delete it from
577
+ // the target object to allow it to fall back to the prototype chain as it did originally.
578
+ if (!Reflect.deleteProperty(target, propertyKey)) {
579
+ throw new Error(`Failed to delete transient function "${propertyKey.toString()}" on object "${target}".`);
580
+ }
581
+ }
582
+ }
583
+ isDisposed = true;
584
+ }
585
+ },
586
+ };
587
+ }
588
+
576
589
  /**
577
590
  * Provides an observable that fires when a specified function/property is called/set.
578
591
  * @param type The type of the interceptor, either "function" or "property".
@@ -583,6 +596,7 @@ function InterceptProperty(target, propertyKey, hooks) {
583
596
  function useInterceptObservable(type, target, propertyKey) {
584
597
  // Create a cached observable. It effectively has the lifetime of the component that uses this hook.
585
598
  const observable = useMemo(() => new Observable(), []);
599
+ const watcher = useWatcher();
586
600
  // Whenever the type, target, or property key changes, we need to set up a new interceptor.
587
601
  useEffect(() => {
588
602
  let interceptToken = null;
@@ -595,11 +609,7 @@ function useInterceptObservable(type, target, propertyKey) {
595
609
  });
596
610
  }
597
611
  else if (type === "property") {
598
- interceptToken = InterceptProperty(target, propertyKey, {
599
- afterSet: () => {
600
- observable.notifyObservers();
601
- },
602
- });
612
+ interceptToken = watcher.watchProperty(target, propertyKey, () => observable.notifyObservers());
603
613
  }
604
614
  else {
605
615
  throw new Error(`Unknown interceptor type: ${type}`);
@@ -1068,14 +1078,20 @@ function useKeyState(key, options) {
1068
1078
  useKeyListener({
1069
1079
  onKeyDown: useCallback((e) => {
1070
1080
  if (e.key === key) {
1081
+ if (options?.preventDefault) {
1082
+ e.preventDefault();
1083
+ }
1071
1084
  setIsPressed(true);
1072
1085
  }
1073
- }, [key]),
1086
+ }, [key, options?.preventDefault]),
1074
1087
  onKeyUp: useCallback((e) => {
1075
1088
  if (e.key === key) {
1089
+ if (options?.preventDefault) {
1090
+ e.preventDefault();
1091
+ }
1076
1092
  setIsPressed(false);
1077
1093
  }
1078
- }, [key]),
1094
+ }, [key, options?.preventDefault]),
1079
1095
  }, options);
1080
1096
  useEventListener("window", "blur", useCallback(() => setIsPressed(false), []), options); // Reset state on window blur to avoid stuck keys
1081
1097
  return isPressed;
@@ -1338,7 +1354,7 @@ function useIsSectionEmpty(sectionId) {
1338
1354
  return hasItems;
1339
1355
  }
1340
1356
 
1341
- const useStyles$S = makeStyles({
1357
+ const useStyles$T = makeStyles({
1342
1358
  accordion: {
1343
1359
  display: "flex",
1344
1360
  flexDirection: "column",
@@ -1416,6 +1432,7 @@ const useStyles$S = makeStyles({
1416
1432
  },
1417
1433
  searchBox: {
1418
1434
  width: "100%",
1435
+ maxWidth: "none",
1419
1436
  },
1420
1437
  });
1421
1438
  /**
@@ -1425,19 +1442,19 @@ const useStyles$S = makeStyles({
1425
1442
  */
1426
1443
  const AccordionMenuBar = () => {
1427
1444
  AccordionMenuBar.displayName = "AccordionMenuBar";
1428
- const classes = useStyles$S();
1445
+ const classes = useStyles$T();
1429
1446
  const accordionCtx = useContext(AccordionContext);
1430
1447
  if (!accordionCtx) {
1431
1448
  return null;
1432
1449
  }
1433
1450
  const { state, dispatch, features } = accordionCtx;
1434
1451
  const { editMode } = state;
1435
- return (jsxs("div", { className: classes.menuBar, children: [jsx(AccordionSearchBox, {}), jsxs("div", { className: classes.menuBarControls, children: [features.hiding && editMode && (jsxs(Fragment, { children: [jsx(Button, { title: "Show all", icon: EyeFilled, appearance: "subtle", onClick: () => dispatch({ type: "SHOW_ALL" }) }), jsx(Button, { title: "Hide all", icon: EyeOffRegular, appearance: "subtle", onClick: () => {
1452
+ return (jsxs("div", { className: classes.menuBar, children: [jsx(AccordionSearchBox, {}), jsxs("div", { className: classes.menuBarControls, children: [features.hiding && editMode && (jsxs(Fragment, { children: [jsx(Button, { title: "Show all", icon: EyeFilled, appearance: "transparent", onClick: () => dispatch({ type: "SHOW_ALL" }) }), jsx(Button, { title: "Hide all", icon: EyeOffRegular, appearance: "transparent", onClick: () => {
1436
1453
  // Hide all visible (non-hidden) items using the registered item IDs
1437
1454
  const { registeredItemIds, state: currentState } = accordionCtx;
1438
1455
  const visibleItemIds = Array.from(registeredItemIds.keys()).filter((id) => !currentState.hiddenIds.includes(id));
1439
1456
  dispatch({ type: "HIDE_ALL_VISIBLE", visibleItemIds });
1440
- } })] })), (features.pinning || features.hiding) && (jsx(Button, { title: "Edit mode", icon: editMode ? CheckmarkFilled : EditRegular, appearance: editMode ? "primary" : "subtle", onClick: () => dispatch({ type: "SET_EDIT_MODE", enabled: !editMode }) }))] })] }));
1457
+ } })] })), (features.pinning || features.hiding) && (jsx(Button, { title: "Edit mode", icon: editMode ? CheckmarkFilled : EditRegular, appearance: editMode ? "primary" : "transparent", onClick: () => dispatch({ type: "SET_EDIT_MODE", enabled: !editMode }) }))] })] }));
1441
1458
  };
1442
1459
  /**
1443
1460
  * Wrapper component that must encapsulate the section headers and panels.
@@ -1468,7 +1485,7 @@ const AccordionSectionBlock = (props) => {
1468
1485
  const AccordionSectionItem = (props) => {
1469
1486
  AccordionSectionItem.displayName = "AccordionSectionItem";
1470
1487
  const { children, staticItem } = props;
1471
- const classes = useStyles$S();
1488
+ const classes = useStyles$T();
1472
1489
  const accordionCtx = useContext(AccordionContext);
1473
1490
  const itemState = useAccordionSectionItemState(props);
1474
1491
  const [ctrlMode, setCtrlMode] = useState(false);
@@ -1508,7 +1525,7 @@ const AccordionSectionItem = (props) => {
1508
1525
  */
1509
1526
  const AccordionPinnedContainer = () => {
1510
1527
  AccordionPinnedContainer.displayName = "AccordionPinnedContainer";
1511
- const classes = useStyles$S();
1528
+ const classes = useStyles$T();
1512
1529
  const accordionCtx = useContext(AccordionContext);
1513
1530
  return (jsx("div", { ref: accordionCtx?.pinnedContainerRef, className: classes.pinnedContainer, children: jsx(MessageBar$1, { className: classes.pinnedContainerEmpty, children: jsx(MessageBarBody, { children: "No pinned items" }) }) }));
1514
1531
  };
@@ -1519,7 +1536,7 @@ const AccordionPinnedContainer = () => {
1519
1536
  */
1520
1537
  const AccordionSearchBox = () => {
1521
1538
  AccordionSearchBox.displayName = "AccordionSearchBox";
1522
- const classes = useStyles$S();
1539
+ const classes = useStyles$T();
1523
1540
  const accordionCtx = useContext(AccordionContext);
1524
1541
  if (!accordionCtx?.features.search) {
1525
1542
  return null;
@@ -1535,14 +1552,15 @@ const AccordionSearchBox = () => {
1535
1552
  */
1536
1553
  const AccordionSection = (props) => {
1537
1554
  AccordionSection.displayName = "AccordionSection";
1538
- const classes = useStyles$S();
1555
+ const classes = useStyles$T();
1539
1556
  return jsx("div", { className: classes.panelDiv, children: props.children });
1540
1557
  };
1541
1558
  const StringAccordion = Accordion$1;
1542
1559
  const Accordion = forwardRef((props, ref) => {
1543
1560
  Accordion.displayName = "Accordion";
1544
- const { children, highlightSections, ...rest } = props;
1545
- const classes = useStyles$S();
1561
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
1562
+ const { children, highlightSections, uniqueId, enablePinnedItems, enableHiddenItems, enableSearchItems, ...rest } = props;
1563
+ const classes = useStyles$T();
1546
1564
  const { size } = useContext(ToolContext);
1547
1565
  const accordionCtx = useAccordionContext(props);
1548
1566
  const hasPinning = accordionCtx?.features.pinning ?? false;
@@ -1639,7 +1657,7 @@ const Collapse = (props) => {
1639
1657
  return (jsx(Collapse$1, { visible: props.visible, orientation: props.orientation, unmountOnExit: true, children: jsx("div", { className: `${classes.collapseContent} ${props.orientation === "horizontal" ? classes.horizontal : classes.vertical}`, children: props.children }) }));
1640
1658
  };
1641
1659
 
1642
- const useStyles$R = makeStyles({
1660
+ const useStyles$S = makeStyles({
1643
1661
  button: {
1644
1662
  display: "flex",
1645
1663
  alignItems: "center",
@@ -1657,7 +1675,7 @@ const ToggleButton = (props) => {
1657
1675
  ToggleButton.displayName = "ToggleButton";
1658
1676
  const { value, onChange, title, appearance = "subtle" } = props;
1659
1677
  const { size } = useContext(ToolContext);
1660
- const classes = useStyles$R();
1678
+ const classes = useStyles$S();
1661
1679
  const [checked, setChecked] = useState(value);
1662
1680
  const toggle = useCallback(() => {
1663
1681
  setChecked((prevChecked) => {
@@ -1908,11 +1926,11 @@ const CompactModeSettingDescriptor = {
1908
1926
  };
1909
1927
  const UseDegreesSettingDescriptor = {
1910
1928
  key: "UseDegrees",
1911
- defaultValue: false,
1929
+ defaultValue: true,
1912
1930
  };
1913
1931
  const UseEulerSettingDescriptor = {
1914
1932
  key: "UseEuler",
1915
- defaultValue: false,
1933
+ defaultValue: true,
1916
1934
  };
1917
1935
  const DisableCopySettingDescriptor = {
1918
1936
  key: "DisableCopy",
@@ -1996,7 +2014,7 @@ const UXContextProvider = (props) => {
1996
2014
  function AsReadonlyArray(array) {
1997
2015
  return array;
1998
2016
  }
1999
- const useStyles$Q = makeStyles({
2017
+ const useStyles$R = makeStyles({
2000
2018
  rootDiv: {
2001
2019
  flex: 1,
2002
2020
  overflow: "hidden",
@@ -2005,7 +2023,7 @@ const useStyles$Q = makeStyles({
2005
2023
  },
2006
2024
  });
2007
2025
  function ExtensibleAccordion(props) {
2008
- const classes = useStyles$Q();
2026
+ const classes = useStyles$R();
2009
2027
  const { children, sections, sectionContent, context, sectionsRef, ...rest } = props;
2010
2028
  const defaultSections = useMemo(() => {
2011
2029
  const defaultSections = [];
@@ -2130,7 +2148,7 @@ function ExtensibleAccordion(props) {
2130
2148
  })] }) })) }));
2131
2149
  }
2132
2150
 
2133
- const useStyles$P = makeStyles({
2151
+ const useStyles$Q = makeStyles({
2134
2152
  paneRootDiv: {
2135
2153
  display: "flex",
2136
2154
  flex: 1,
@@ -2143,7 +2161,7 @@ const useStyles$P = makeStyles({
2143
2161
  */
2144
2162
  const SidePaneContainer = forwardRef((props, ref) => {
2145
2163
  const { className, ...rest } = props;
2146
- const classes = useStyles$P();
2164
+ const classes = useStyles$Q();
2147
2165
  return (jsx("div", { className: mergeClasses(classes.paneRootDiv, className), ref: ref, ...rest, children: props.children }));
2148
2166
  });
2149
2167
 
@@ -2393,7 +2411,7 @@ const Theme = (props) => {
2393
2411
  return (jsx(FluentProvider, { theme: theme, applyStylesToPortals: applyStylesToPortals, ...rest, children: props.children }));
2394
2412
  };
2395
2413
 
2396
- const useStyles$O = makeStyles({
2414
+ const useStyles$P = makeStyles({
2397
2415
  extensionTeachingPopover: {
2398
2416
  maxWidth: "320px",
2399
2417
  },
@@ -2404,7 +2422,7 @@ const useStyles$O = makeStyles({
2404
2422
  * @returns The teaching moment popover.
2405
2423
  */
2406
2424
  const TeachingMoment = ({ shouldDisplay, positioningRef, onOpenChange, title, description }) => {
2407
- const classes = useStyles$O();
2425
+ const classes = useStyles$P();
2408
2426
  return (jsx(TeachingPopover, { appearance: "brand", open: shouldDisplay, positioning: { positioningRef }, onOpenChange: onOpenChange, children: jsxs(TeachingPopoverSurface, { className: classes.extensionTeachingPopover, children: [jsx(TeachingPopoverHeader, { children: title }), jsx(TeachingPopoverBody, { children: description })] }) }));
2409
2427
  };
2410
2428
 
@@ -2588,13 +2606,32 @@ function ConstructorFactory(constructor) {
2588
2606
  return (...args) => new constructor(...args);
2589
2607
  }
2590
2608
 
2591
- const useStyles$N = makeStyles({
2609
+ /**
2610
+ * A hook that provides a transient state value and a "pulse" function to set it.
2611
+ * The transient value is meant to be consumed immediately after being set, and will be cleared on the next render.
2612
+ * @typeParam T The type of the transient value.
2613
+ * @returns A tuple containing the transient value and a function to "pulse" the state.
2614
+ */
2615
+ function useImpulse() {
2616
+ const impulseRef = useRef(undefined);
2617
+ const [, setVersion] = useState(0);
2618
+ const pulse = useCallback((value) => {
2619
+ impulseRef.current = value;
2620
+ setVersion((v) => v + 1);
2621
+ }, []);
2622
+ // Consume the impulse value and clear it
2623
+ const value = impulseRef.current;
2624
+ impulseRef.current = undefined;
2625
+ return [value, pulse];
2626
+ }
2627
+
2628
+ const useStyles$O = makeStyles({
2592
2629
  placeholderDiv: {
2593
2630
  padding: `${tokens.spacingVerticalM} ${tokens.spacingHorizontalM}`,
2594
2631
  },
2595
2632
  });
2596
2633
  const PropertiesPane = (props) => {
2597
- const classes = useStyles$N();
2634
+ const classes = useStyles$O();
2598
2635
  const entity = props.context;
2599
2636
  return entity != null ? (jsx(ExtensibleAccordion, { ...props })) : (jsx("div", { className: classes.placeholderDiv, children: jsx(Body1Strong, { italic: true, children: "No entity selected." }) }));
2600
2637
  };
@@ -2896,7 +2933,7 @@ const RightSidePaneHeightAdjustSettingDescriptor = {
2896
2933
  };
2897
2934
  const RootComponentServiceIdentity = Symbol("RootComponent");
2898
2935
  const ShellServiceIdentity = Symbol("ShellService");
2899
- const useStyles$M = makeStyles({
2936
+ const useStyles$N = makeStyles({
2900
2937
  mainView: {
2901
2938
  flex: 1,
2902
2939
  display: "flex",
@@ -3099,34 +3136,38 @@ const DockMenu = (props) => {
3099
3136
  };
3100
3137
  const PaneHeader = (props) => {
3101
3138
  const { id, title, dockOptions } = props;
3102
- const classes = useStyles$M();
3139
+ const classes = useStyles$N();
3103
3140
  return (jsxs("div", { className: classes.paneHeaderDiv, children: [jsx(Subtitle2Stronger, { className: classes.paneHeaderText, children: title }), jsx(DockMenu, { sidePaneId: id, dockOptions: dockOptions, children: jsx(Button$1, { className: classes.paneHeaderButton, appearance: "transparent", icon: jsx(MoreHorizontalRegular, {}) }) })] }));
3104
3141
  };
3105
3142
  // This is a wrapper for an item in a toolbar that simply adds a teaching moment, which is useful for dynamically added items, possibly from extensions.
3106
- const ToolbarItem = ({ verticalLocation, horizontalLocation, id, component: Component, displayName: displayName, suppressTeachingMoment }) => {
3107
- const classes = useStyles$M();
3143
+ const ToolbarItem = (props) => {
3144
+ // eslint-disable-next-line @typescript-eslint/naming-convention
3145
+ const { verticalLocation, horizontalLocation, id, component: Component, displayName } = props;
3146
+ const classes = useStyles$N();
3108
3147
  const useTeachingMoment = useMemo(() => MakePopoverTeachingMoment(`Bar/${verticalLocation}/${horizontalLocation}/${displayName ?? id}`), [displayName, id]);
3109
- const teachingMoment = useTeachingMoment(suppressTeachingMoment);
3110
- return (jsxs(Fragment, { children: [jsx(TeachingMoment, { ...teachingMoment, shouldDisplay: teachingMoment.shouldDisplay && !suppressTeachingMoment, title: displayName ?? "Extension", description: `The "${displayName ?? id}" extension can be accessed here.` }), jsx("div", { className: classes.barItem, ref: teachingMoment.targetRef, children: jsx(Component, {}) })] }));
3148
+ const teachingMoment = useTeachingMoment(props.teachingMoment === false);
3149
+ const title = typeof props.teachingMoment === "object" ? props.teachingMoment.title : (displayName ?? id);
3150
+ const description = typeof props.teachingMoment === "object" ? props.teachingMoment.description : `The "${displayName ?? id}" extension can be accessed here.`;
3151
+ return (jsxs(Fragment, { children: [jsx(TeachingMoment, { ...teachingMoment, shouldDisplay: teachingMoment.shouldDisplay, title: title, description: description }), jsx("div", { className: classes.barItem, ref: teachingMoment.targetRef, children: jsx(Component, {}) })] }));
3111
3152
  };
3112
3153
  // TODO: Handle overflow, possibly via https://react.fluentui.dev/?path=/docs/components-overflow--docs with priority.
3113
3154
  // This component just renders a toolbar with left aligned toolbar items on the left and right aligned toolbar items on the right.
3114
3155
  const Toolbar = ({ location, components }) => {
3115
- const classes = useStyles$M();
3156
+ const classes = useStyles$N();
3116
3157
  const leftComponents = useMemo(() => components.filter((entry) => entry.horizontalLocation === "left"), [components]);
3117
3158
  const rightComponents = useMemo(() => components.filter((entry) => entry.horizontalLocation === "right"), [components]);
3118
- return (jsx(Fragment, { children: components.length > 0 && (jsxs("div", { className: `${classes.bar} ${location === "top" ? classes.barTop : classes.barBottom}`, children: [jsx("div", { className: classes.barLeft, children: leftComponents.map((entry) => (jsx(ToolbarItem, { verticalLocation: location, horizontalLocation: entry.horizontalLocation, id: entry.key, component: entry.component, displayName: entry.displayName, suppressTeachingMoment: entry.suppressTeachingMoment }, entry.key))) }), jsx("div", { className: classes.barRight, children: rightComponents.map((entry) => (jsx(ToolbarItem, { verticalLocation: location, horizontalLocation: entry.horizontalLocation, id: entry.key, component: entry.component, displayName: entry.displayName, suppressTeachingMoment: entry.suppressTeachingMoment }, entry.key))) })] })) }));
3159
+ return (jsx(Fragment, { children: components.length > 0 && (jsxs("div", { className: `${classes.bar} ${location === "top" ? classes.barTop : classes.barBottom}`, children: [jsx("div", { className: classes.barLeft, children: leftComponents.map((entry) => (jsx(ToolbarItem, { verticalLocation: location, horizontalLocation: entry.horizontalLocation, id: entry.key, component: entry.component, displayName: entry.displayName, teachingMoment: entry.teachingMoment }, entry.key))) }), jsx("div", { className: classes.barRight, children: rightComponents.map((entry) => (jsx(ToolbarItem, { verticalLocation: location, horizontalLocation: entry.horizontalLocation, id: entry.key, component: entry.component, displayName: entry.displayName, teachingMoment: entry.teachingMoment }, entry.key))) })] })) }));
3119
3160
  };
3120
3161
  // This is a wrapper for a tab in a side pane that simply adds a teaching moment, which is useful for dynamically added items, possibly from extensions.
3121
3162
  const SidePaneTab = (props) => {
3122
3163
  const { location, id, isSelected, isFirst, isLast, dockOptions,
3123
3164
  // eslint-disable-next-line @typescript-eslint/naming-convention
3124
- icon: Icon, title, suppressTeachingMoment, } = props;
3125
- const classes = useStyles$M();
3165
+ icon: Icon, title, } = props;
3166
+ const classes = useStyles$N();
3126
3167
  const useTeachingMoment = useMemo(() => MakePopoverTeachingMoment(`Pane/${location}/${title ?? id}`), [title, id]);
3127
- const teachingMoment = useTeachingMoment(suppressTeachingMoment);
3168
+ const teachingMoment = useTeachingMoment(props.teachingMoment === false);
3128
3169
  const tabClass = mergeClasses(classes.tab, isSelected ? classes.selectedTab : classes.unselectedTab, isFirst ? classes.firstTab : undefined, isLast ? classes.lastTab : undefined);
3129
- return (jsxs(Fragment, { children: [jsx(TeachingMoment, { ...teachingMoment, shouldDisplay: teachingMoment.shouldDisplay && !suppressTeachingMoment, title: title ?? "Extension", description: `The "${title ?? id}" extension can be accessed here.` }), jsx("div", { className: tabClass, children: jsx(DockMenu, { openOnContext: true, sidePaneId: id, dockOptions: dockOptions, children: jsx(Tooltip, { content: title ?? id, children: jsx(ToolbarRadioButton, { ref: teachingMoment.targetRef, appearance: "transparent", className: classes.tabRadioButton, name: "selectedTab", value: id, icon: {
3170
+ return (jsxs(Fragment, { children: [jsx(TeachingMoment, { ...teachingMoment, shouldDisplay: teachingMoment.shouldDisplay, title: typeof props.teachingMoment === "object" ? props.teachingMoment.title : (title ?? "Extension"), description: typeof props.teachingMoment === "object" ? props.teachingMoment.description : `The "${title ?? id}" extension can be accessed here.` }), jsx("div", { className: tabClass, children: jsx(DockMenu, { openOnContext: true, sidePaneId: id, dockOptions: dockOptions, children: jsx(Tooltip, { content: title ?? id, children: jsx(ToolbarRadioButton, { ref: teachingMoment.targetRef, appearance: "transparent", className: classes.tabRadioButton, name: "selectedTab", value: id, icon: {
3130
3171
  children: jsx(Icon, {}),
3131
3172
  } }) }) }) })] }));
3132
3173
  };
@@ -3134,7 +3175,7 @@ const SidePaneTab = (props) => {
3134
3175
  // In "compact" mode, the tab list is integrated into the pane itself.
3135
3176
  // In "full" mode, the returned tab list is later injected into the toolbar.
3136
3177
  function usePane(location, defaultWidth, minWidth, sidePanes, onSelectSidePane, dockOperations, toolbarMode, topBarItems, bottomBarItems, initialCollapsed) {
3137
- const classes = useStyles$M();
3178
+ const classes = useStyles$N();
3138
3179
  const [topSelectedTab, setTopSelectedTab] = useState();
3139
3180
  const [bottomSelectedTab, setBottomSelectedTab] = useState();
3140
3181
  const [collapsed, setCollapsed] = useState(initialCollapsed);
@@ -3236,7 +3277,7 @@ function usePane(location, defaultWidth, minWidth, sidePanes, onSelectSidePane,
3236
3277
  setCollapsed(false);
3237
3278
  }, children: paneComponents.map((entry, index) => {
3238
3279
  const isSelected = selectedTab?.key === entry.key;
3239
- return (jsx(SidePaneTab, { location: location, id: entry.key, title: entry.title, icon: entry.icon, suppressTeachingMoment: entry.suppressTeachingMoment, isSelected: isSelected && !collapsed, isFirst: index === 0, isLast: index === paneComponents.length - 1, dockOptions: dockOptions }, entry.key));
3280
+ return (jsx(SidePaneTab, { location: location, id: entry.key, title: entry.title, icon: entry.icon, teachingMoment: entry.teachingMoment, isSelected: isSelected && !collapsed, isFirst: index === 0, isLast: index === paneComponents.length - 1, dockOptions: dockOptions }, entry.key));
3240
3281
  }) }) })), toolbarMode === "full" && (jsx(Collapse, { visible: !isChildWindowOpen, orientation: "horizontal", children: expandCollapseButton }))] })) }));
3241
3282
  }, [location, collapsed, isChildWindowOpen, expandCollapseButton]);
3242
3283
  // This memos the TabList to make it easy for the JSX to be inserted at the top of the pane (in "compact" mode) or returned to the caller to be used in the toolbar (in "full" mode).
@@ -3331,7 +3372,7 @@ function MakeShellServiceDefinition({ leftPaneDefaultWidth = 350, leftPaneMinWid
3331
3372
  expand: () => onCollapseChanged.notifyObservers({ location: "right", collapsed: false }),
3332
3373
  };
3333
3374
  const rootComponent = () => {
3334
- const classes = useStyles$M();
3375
+ const classes = useStyles$N();
3335
3376
  const [sidePaneDockOverrides, setSidePaneDockOverrides] = useSetting(SidePaneDockOverridesSettingDescriptor);
3336
3377
  // This function returns a promise that resolves after the dock change takes effect so that
3337
3378
  // we can then select the re-docked pane.
@@ -3566,7 +3607,7 @@ const SettingsServiceDefinition = {
3566
3607
  horizontalLocation: "right",
3567
3608
  verticalLocation: "top",
3568
3609
  order: 500,
3569
- suppressTeachingMoment: true,
3610
+ teachingMoment: false,
3570
3611
  content: () => {
3571
3612
  const sections = useOrderedObservableCollection(sectionsCollection);
3572
3613
  const sectionContent = useObservableCollection(sectionContentCollection);
@@ -3664,7 +3705,7 @@ const PropertiesServiceDefinition = {
3664
3705
  const sectionsCollection = new ObservableCollection();
3665
3706
  const sectionContentCollection = new ObservableCollection();
3666
3707
  const onPropertyChanged = new Observable();
3667
- const onHighlightSectionsRequested = new Observable();
3708
+ const onHighlightSectionsRequested = new Observable(undefined, true);
3668
3709
  const registration = shellService.addSidePane({
3669
3710
  key: "Properties",
3670
3711
  title: "Properties",
@@ -3672,7 +3713,7 @@ const PropertiesServiceDefinition = {
3672
3713
  horizontalLocation: "right",
3673
3714
  verticalLocation: "top",
3674
3715
  order: 100,
3675
- suppressTeachingMoment: true,
3716
+ teachingMoment: false,
3676
3717
  keepMounted: true,
3677
3718
  content: () => {
3678
3719
  const sections = useOrderedObservableCollection(sectionsCollection);
@@ -3696,15 +3737,23 @@ const PropertiesServiceDefinition = {
3696
3737
  // The selected entity may be set at the same time as a highlight is requested.
3697
3738
  // To account for this, we need to wait for one React render to complete before
3698
3739
  // requesting the section highlight.
3699
- const [pendingHighlight, setPendingHighlight] = useState();
3740
+ const [pendingHighlight, pulsePendingHighlightSections] = useImpulse();
3700
3741
  useEffect(() => {
3701
- const observer = onHighlightSectionsRequested.add(setPendingHighlight);
3702
- return () => observer.remove();
3742
+ const observer = onHighlightSectionsRequested.add((sectionIds) => {
3743
+ // Now this UI component is observing, so we don't need to cache pending requests anymore.
3744
+ onHighlightSectionsRequested.notifyIfTriggered = false;
3745
+ onHighlightSectionsRequested.cleanLastNotifiedState();
3746
+ pulsePendingHighlightSections(sectionIds);
3747
+ });
3748
+ return () => {
3749
+ observer.remove();
3750
+ // Now this UI component is no longer observing, so we need to cache pending requests again.
3751
+ onHighlightSectionsRequested.notifyIfTriggered = true;
3752
+ };
3703
3753
  }, []);
3704
3754
  useEffect(() => {
3705
3755
  if (pendingHighlight && sectionsRef.current) {
3706
3756
  sectionsRef.current.highlightSections(pendingHighlight);
3707
- setPendingHighlight(undefined);
3708
3757
  }
3709
3758
  }, [pendingHighlight]);
3710
3759
  return (jsx(PropertyContext.Provider, { value: { onPropertyChanged }, children: jsx(PropertiesPane, { uniqueId: "Properties", sections: sections, sectionContent: applicableContent, context: entity, sectionsRef: sectionsRef, enablePinnedItems: true, enableHiddenItems: true, enableSearchItems: true }) }));
@@ -4076,7 +4125,7 @@ function CoerceEntityArray(entities, sort) {
4076
4125
  }
4077
4126
  return entities;
4078
4127
  }
4079
- const useStyles$L = makeStyles({
4128
+ const useStyles$M = makeStyles({
4080
4129
  rootDiv: {
4081
4130
  flex: 1,
4082
4131
  overflow: "hidden",
@@ -4092,6 +4141,7 @@ const useStyles$L = makeStyles({
4092
4141
  searchBox: {
4093
4142
  flex: 1,
4094
4143
  padding: 0,
4144
+ maxWidth: "none",
4095
4145
  },
4096
4146
  tree: {
4097
4147
  rowGap: 0,
@@ -4184,14 +4234,14 @@ function MakeInlineCommandElement(command, isPlaceholder) {
4184
4234
  }
4185
4235
  const SceneTreeItem = (props) => {
4186
4236
  const { isSelected, select } = props;
4187
- const classes = useStyles$L();
4237
+ const classes = useStyles$M();
4188
4238
  const [compactMode] = useSetting(CompactModeSettingDescriptor);
4189
4239
  const treeItemLayoutClass = mergeClasses(classes.sceneTreeItemLayout, compactMode ? classes.treeItemLayoutCompact : undefined);
4190
4240
  return (jsx(FlatTreeItem, { className: classes.treeItem, value: "scene", itemType: "leaf", parentValue: undefined, "aria-level": 1, "aria-setsize": 1, "aria-posinset": 1, onClick: select, children: jsx(TreeItemLayout, { iconBefore: jsx(GlobeRegular, {}), className: treeItemLayoutClass, style: isSelected ? { backgroundColor: tokens.colorNeutralBackground1Selected } : undefined, children: jsx(Body1Strong, { wrap: false, truncate: true, children: "Scene" }) }) }, "scene"));
4191
4241
  };
4192
4242
  const SectionTreeItem = (props) => {
4193
4243
  const { section, isFiltering, commandProviders, expandAll, collapseAll, isDropTarget, ...dropProps } = props;
4194
- const classes = useStyles$L();
4244
+ const classes = useStyles$M();
4195
4245
  const [compactMode] = useSetting(CompactModeSettingDescriptor);
4196
4246
  // Get the commands that apply to this section.
4197
4247
  const commands = useResource(useCallback(() => {
@@ -4208,7 +4258,7 @@ const SectionTreeItem = (props) => {
4208
4258
  };
4209
4259
  const EntityTreeItem = (props) => {
4210
4260
  const { entityItem, isSelected, select, isFiltering, commandProviders, expandAll, collapseAll, isDragging, isDropTarget, ...dragProps } = props;
4211
- const classes = useStyles$L();
4261
+ const classes = useStyles$M();
4212
4262
  const [compactMode] = useSetting(CompactModeSettingDescriptor);
4213
4263
  const hasChildren = !!entityItem.children?.length;
4214
4264
  const displayInfo = useResource(useCallback(() => {
@@ -4324,7 +4374,7 @@ const EntityTreeItem = (props) => {
4324
4374
  }, children: jsx(Body1, { wrap: false, truncate: true, children: name }) }) }, GetEntityId(entityItem.entity)) }), jsx(MenuPopover, { hidden: !hasChildren && contextMenuCommands.length === 0, children: jsxs(MenuList, { children: [hasChildren && (jsxs(Fragment, { children: [jsx(MenuItem, { icon: jsx(ArrowExpandAllRegular, {}), onClick: expandAll, children: jsx(Body1, { children: "Expand All" }) }), jsx(MenuItem, { icon: jsx(ArrowCollapseAllRegular, {}), onClick: collapseAll, children: jsx(Body1, { children: "Collapse All" }) })] })), hasChildren && contextMenuCommands.length > 0 && jsx(MenuDivider, {}), contextMenuItems] }) })] }));
4325
4375
  };
4326
4376
  const SceneExplorer = (props) => {
4327
- const classes = useStyles$L();
4377
+ const classes = useStyles$M();
4328
4378
  const { sections, entityCommandProviders, sectionCommandProviders, scene, selectedEntity = null } = props;
4329
4379
  const [openItems, setOpenItems] = useState(new Set());
4330
4380
  const [sceneVersion, setSceneVersion] = useState(0);
@@ -4617,7 +4667,7 @@ const SceneExplorerServiceDefinition = {
4617
4667
  icon: CubeTreeRegular,
4618
4668
  horizontalLocation: "left",
4619
4669
  verticalLocation: "top",
4620
- suppressTeachingMoment: true,
4670
+ teachingMoment: false,
4621
4671
  keepMounted: true,
4622
4672
  content: () => {
4623
4673
  const sections = useOrderedObservableCollection(sectionsCollection);
@@ -4778,7 +4828,7 @@ const DebugServiceDefinition = {
4778
4828
  horizontalLocation: "right",
4779
4829
  verticalLocation: "top",
4780
4830
  order: 200,
4781
- suppressTeachingMoment: true,
4831
+ teachingMoment: false,
4782
4832
  content: () => {
4783
4833
  const sections = useOrderedObservableCollection(sectionsCollection);
4784
4834
  const sectionContent = useObservableCollection(sectionContentCollection);
@@ -4901,7 +4951,7 @@ const UploadButton = (props) => {
4901
4951
  */
4902
4952
  const FileUploadLine = ({ onClick, label, accept, ...buttonProps }) => {
4903
4953
  FileUploadLine.displayName = "FileUploadLine";
4904
- return (jsx(LineContainer, { uniqueId: label, children: jsx(UploadButton, { onUpload: onClick, accept: accept, label: label, ...buttonProps }) }));
4954
+ return (jsx(LineContainer, { uniqueId: `${label}_upload`, label: label, children: jsx(UploadButton, { onUpload: onClick, accept: accept, label: label, ...buttonProps }) }));
4905
4955
  };
4906
4956
 
4907
4957
  /**
@@ -5947,7 +5997,7 @@ class CanvasGraphService {
5947
5997
  }
5948
5998
  }
5949
5999
 
5950
- const useStyles$K = makeStyles({
6000
+ const useStyles$L = makeStyles({
5951
6001
  canvas: {
5952
6002
  flexGrow: 1,
5953
6003
  width: "100%",
@@ -5956,7 +6006,7 @@ const useStyles$K = makeStyles({
5956
6006
  });
5957
6007
  const CanvasGraph = (props) => {
5958
6008
  const { collector, scene, layoutObservable, returnToPlayheadObservable, onVisibleRangeChangedObservable, initialGraphSize } = props;
5959
- const classes = useStyles$K();
6009
+ const classes = useStyles$L();
5960
6010
  const canvasRef = useRef(null);
5961
6011
  useEffect(() => {
5962
6012
  if (!canvasRef.current) {
@@ -6010,135 +6060,208 @@ const CanvasGraph = (props) => {
6010
6060
  return jsx("canvas", { className: classes.canvas, ref: canvasRef });
6011
6061
  };
6012
6062
 
6013
- function CoerceStepValue(step, isAltKeyPressed, isShiftKeyPressed) {
6014
- // When the alt key is pressed, decrease step by a factor of 10.
6015
- if (isAltKeyPressed) {
6063
+ function CoerceStepValue(step, isFineKeyPressed, isCourseKeyPressed) {
6064
+ // When the fine key is pressed, decrease step by a factor of 10.
6065
+ if (isFineKeyPressed) {
6016
6066
  return step * 0.1;
6017
6067
  }
6018
- // When the shift key is pressed, increase step by a factor of 10.
6019
- if (isShiftKeyPressed) {
6068
+ // When the course key is pressed, increase step by a factor of 10.
6069
+ if (isCourseKeyPressed) {
6020
6070
  return step * 10;
6021
6071
  }
6022
6072
  return step;
6023
6073
  }
6074
+ // Allow arbitrary expressions, primarily for math operations (e.g. 10*60 for 10 minutes in seconds).
6075
+ // Use Function constructor to safely evaluate the expression without allowing access to scope.
6076
+ // If the expression is invalid, fallback to NaN which will be caught by validateValue and prevent committing.
6077
+ function EvaluateExpression(rawValue) {
6078
+ const val = rawValue.trim();
6079
+ try {
6080
+ return Number(Function(`"use strict";return (${val})`)());
6081
+ }
6082
+ catch {
6083
+ return NaN;
6084
+ }
6085
+ }
6086
+ const useStyles$K = makeStyles({
6087
+ icon: {
6088
+ "&:hover": {
6089
+ color: tokens.colorBrandForeground1,
6090
+ },
6091
+ },
6092
+ });
6093
+ /**
6094
+ * A numeric input with a vertical drag-to-scrub icon (ArrowsBidirectionalRegular rotated 90°).
6095
+ * Click-and-drag up/down on the icon to increment/decrement the value.
6096
+ */
6024
6097
  const SpinButton = forwardRef((props, ref) => {
6025
- SpinButton.displayName = "SpinButton";
6026
- const classes = useInputStyles$1();
6098
+ SpinButton.displayName = "SpinButton2";
6099
+ const inputClasses = useInputStyles$1();
6100
+ const classes = useStyles$K();
6027
6101
  const { size } = useContext(ToolContext);
6028
6102
  const { min, max } = props;
6029
- const [value, setValue] = useState(props.value);
6030
- const lastCommittedValue = useRef(props.value);
6031
- // When the input does not have keyboard focus
6032
- const isUnfocusedAltKeyPressed = useKeyState("Alt");
6033
- const isUnfocusedShiftKeyPressed = useKeyState("Shift");
6034
- // When the input does have keyboard focus
6035
- const [isFocusedAltKeyPressed, setIsFocusedAltKeyPressed] = useState(false);
6036
- const [isFocusedShiftKeyPressed, setIsFocusedShiftKeyPressed] = useState(false);
6037
- // step and forceInt are not mutually exclusive since there could be cases where you want to forceInt but have spinButton jump >1 int per spin
6038
- const step = CoerceStepValue(props.step ?? 1, isUnfocusedAltKeyPressed || isFocusedAltKeyPressed, isUnfocusedShiftKeyPressed || isFocusedShiftKeyPressed);
6103
+ const baseStep = props.step ?? 1;
6104
+ // Local ref for the input element so we can blur it programmatically (e.g. when a drag starts while editing).
6105
+ const inputRef = useRef(null);
6106
+ const mergedRef = useMergedRefs(ref, inputRef);
6107
+ // Modifier keys for step coercion.
6108
+ const isAltKeyPressed = useKeyState("Alt", { preventDefault: true });
6109
+ const isShiftKeyPressed = useKeyState("Shift");
6110
+ const step = CoerceStepValue(baseStep, isAltKeyPressed, isShiftKeyPressed);
6039
6111
  const stepPrecision = Math.max(0, CalculatePrecision(step));
6112
+ const [value, setValue] = useState(props.value ?? 0);
6113
+ const lastCommittedValue = useRef(props.value);
6114
+ const [isDragging, setIsDragging] = useState(false);
6115
+ const scrubStartYRef = useRef(0);
6116
+ const scrubStartValueRef = useRef(0);
6117
+ const lastPointerYRef = useRef(0);
6118
+ const [isHovered, setIsHovered] = useState(false);
6119
+ // Editing state: when the user is typing, we show their raw text rather than the formatted value.
6120
+ const [isEditing, setIsEditing] = useState(false);
6121
+ const [editText, setEditText] = useState("");
6040
6122
  const valuePrecision = Math.max(0, CalculatePrecision(value));
6041
- // Display precision: controls how many decimals are shown in the formatted displayValue. Cap at 4 to avoid wild numbers
6042
- const displayPrecision = Math.min(4, Math.max(stepPrecision, valuePrecision));
6043
- // Set to large const to prevent Fluent from rounding user-entered values on commit
6044
- // We control display formatting ourselves via displayValue, so this only affects internal rounding. The value stored internally will still have max precision
6045
- const fluentPrecision = 20;
6123
+ // Display precision: controls how many decimals are shown in the formatted displayValue. Cap at 4 to avoid wild numbers.
6124
+ // If a fixed precision prop is provided, use it instead.
6125
+ const displayPrecision = props.precision ?? Math.min(4, Math.max(stepPrecision, valuePrecision));
6126
+ // Format a number for display: toFixed, then trim trailing zeros and period unless a fixed precision is specified.
6127
+ const formatValue = useCallback((v) => {
6128
+ const fixed = v.toFixed(displayPrecision);
6129
+ if (props.precision !== undefined) {
6130
+ return fixed;
6131
+ }
6132
+ return fixed.replace(/(\.\d*?)0+$/, "$1").replace(/\.$/, "");
6133
+ }, [displayPrecision, props.precision]);
6046
6134
  useEffect(() => {
6047
- if (props.value !== lastCommittedValue.current) {
6135
+ if (!isDragging && props.value !== lastCommittedValue.current) {
6048
6136
  lastCommittedValue.current = props.value;
6049
- setValue(props.value); // Update local state when props.value changes
6137
+ setValue(props.value ?? 0);
6050
6138
  }
6051
- }, [props.value]);
6052
- const validateValue = (numericValue) => {
6139
+ }, [props.value, isDragging]);
6140
+ const validateValue = useCallback((numericValue) => {
6053
6141
  const outOfBounds = (min !== undefined && numericValue < min) || (max !== undefined && numericValue > max);
6054
6142
  const failsValidator = props.validator && !props.validator(numericValue);
6055
6143
  const failsIntCheck = props.forceInt ? !Number.isInteger(numericValue) : false;
6056
6144
  const invalid = !!outOfBounds || !!failsValidator || isNaN(numericValue) || !!failsIntCheck;
6057
6145
  return !invalid;
6058
- };
6059
- const tryCommitValue = (currVal) => {
6060
- // Only commit if valid and different from last committed value
6146
+ }, [min, max, props.validator, props.forceInt]);
6147
+ // Constrain a value to the valid range by clamping to [min, max].
6148
+ const constrainValue = useCallback((v) => Clamp(v, min ?? -Infinity, max ?? Infinity), [min, max]);
6149
+ const tryCommitValue = useCallback((currVal) => {
6061
6150
  if (validateValue(currVal) && currVal !== lastCommittedValue.current) {
6062
6151
  lastCommittedValue.current = currVal;
6063
6152
  props.onChange(currVal);
6064
6153
  }
6065
- };
6066
- const handleChange = (event, data) => {
6067
- event.stopPropagation(); // Prevent event propagation
6068
- if (data.value != null && !Number.isNaN(data.value)) {
6069
- setValue(data.value);
6070
- tryCommitValue(data.value);
6071
- }
6072
- };
6073
- // Strip the unit suffix (e.g. "deg" or " deg") from the raw input value before evaluating expressions.
6074
- const stripUnit = (val) => {
6075
- if (!props.unit) {
6076
- return val;
6077
- }
6078
- const regex = new RegExp("\\s*" + props.unit + "$");
6079
- const match = val.match(regex);
6080
- if (match) {
6081
- return val.slice(0, -match[0].length);
6082
- }
6083
- return val;
6084
- };
6085
- // Allow arbitrary expressions, primarily for math operations (e.g. 10*60 for 10 minutes in seconds).
6086
- // Use Function constructor to safely evaluate the expression without allowing access to scope.
6087
- // If the expression is invalid, fallback to NaN which will be caught by validateValue and prevent committing.
6088
- const evaluateExpression = (rawValue) => {
6089
- const val = stripUnit(rawValue).trim();
6090
- try {
6091
- return Number(Function(`"use strict";return (${val})`)());
6092
- }
6093
- catch {
6094
- return NaN;
6154
+ }, [validateValue, props.onChange]);
6155
+ const handleInputChange = useCallback((_, data) => {
6156
+ // Just update the raw text — no evaluation or commit until Enter/blur.
6157
+ setEditText(data.value);
6158
+ }, []);
6159
+ // Evaluate the current edit text and commit the value. Returns the clamped value if valid, or undefined.
6160
+ const commitEditText = useCallback((text) => {
6161
+ const numericValue = EvaluateExpression(text);
6162
+ if (!isNaN(numericValue) && validateValue(numericValue)) {
6163
+ const constrained = constrainValue(numericValue);
6164
+ setValue(constrained);
6165
+ tryCommitValue(constrained);
6166
+ return constrained;
6095
6167
  }
6096
- };
6097
- const handleKeyDown = (event) => {
6098
- if (event.key === "Alt") {
6099
- setIsFocusedAltKeyPressed(true);
6168
+ return undefined;
6169
+ }, [validateValue, constrainValue, tryCommitValue]);
6170
+ const handleIconPointerDown = useCallback((e) => {
6171
+ e.preventDefault();
6172
+ e.stopPropagation();
6173
+ // If the input was being edited, commit the current text and blur the input
6174
+ // so the focus state stays consistent after the drag ends.
6175
+ let startValue = value;
6176
+ if (isEditing) {
6177
+ const committed = commitEditText(editText);
6178
+ if (committed !== undefined) {
6179
+ startValue = committed;
6180
+ }
6181
+ setIsEditing(false);
6182
+ }
6183
+ // Blur the active element to ensure we can observe document level modifier keys.
6184
+ inputRef.current?.ownerDocument.activeElement?.blur?.();
6185
+ setIsDragging(true);
6186
+ scrubStartYRef.current = e.clientY;
6187
+ scrubStartValueRef.current = startValue;
6188
+ e.currentTarget.setPointerCapture(e.pointerId);
6189
+ }, [value, isEditing, editText, commitEditText]);
6190
+ // When the step size changes during a drag (e.g. Shift/Alt pressed or released), reset the scrub reference point
6191
+ // to the current value and pointer position so only future movement uses the new step.
6192
+ useEffect(() => {
6193
+ if (isDragging) {
6194
+ scrubStartValueRef.current = value;
6195
+ scrubStartYRef.current = lastPointerYRef.current;
6100
6196
  }
6101
- else if (event.key === "Shift") {
6102
- setIsFocusedShiftKeyPressed(true);
6197
+ }, [step]);
6198
+ const handleIconPointerMove = useCallback((e) => {
6199
+ if (!isDragging) {
6200
+ return;
6103
6201
  }
6104
- // Evaluate on Enter in keyDown (before Fluent's internal commit clears the raw text
6105
- // and re-renders with the truncated displayValue).
6202
+ lastPointerYRef.current = e.clientY;
6203
+ // Dragging up (negative dy) should increment, dragging down should decrement.
6204
+ // Scale delta by step but round to display precision (not step) for smooth fine-grained control.
6205
+ const dy = scrubStartYRef.current - e.clientY;
6206
+ // 5 is just a number that "feels right" for the drag sensitivity — it determines how far the user needs to drag to change the value by 1 step.
6207
+ const delta = (dy * step) / 5;
6208
+ const raw = scrubStartValueRef.current + delta;
6209
+ const precisionFactor = Math.pow(10, displayPrecision);
6210
+ const rounded = Math.round(raw * precisionFactor) / precisionFactor;
6211
+ const constrained = constrainValue(rounded);
6212
+ setValue(constrained);
6213
+ tryCommitValue(constrained);
6214
+ }, [isDragging, step, displayPrecision, constrainValue, tryCommitValue]);
6215
+ const handleIconPointerUp = useCallback((e) => {
6216
+ setIsDragging(false);
6217
+ e.currentTarget.releasePointerCapture(e.pointerId);
6218
+ }, []);
6219
+ const handleKeyDown = useCallback((event) => {
6220
+ // Commit on Enter and blur the input if the value is valid.
6106
6221
  if (event.key === "Enter") {
6107
- const currVal = evaluateExpression(event.target.value);
6108
- if (!isNaN(currVal)) {
6109
- setValue(currVal);
6110
- tryCommitValue(currVal);
6222
+ const committed = commitEditText(event.currentTarget.value);
6223
+ if (committed !== undefined) {
6224
+ inputRef.current?.blur();
6111
6225
  }
6112
6226
  }
6113
- HandleKeyDown(event);
6114
- };
6115
- const handleKeyUp = (event) => {
6116
- event.stopPropagation(); // Prevent event propagation
6117
- if (event.key === "Alt") {
6118
- setIsFocusedAltKeyPressed(false);
6119
- }
6120
- else if (event.key === "Shift") {
6121
- setIsFocusedShiftKeyPressed(false);
6227
+ if (event.key === "ArrowUp" || event.key === "ArrowDown") {
6228
+ event.preventDefault();
6229
+ const direction = event.key === "ArrowUp" ? 1 : -1;
6230
+ const newValue = constrainValue(Math.round((value + direction * step) / step) * step);
6231
+ setValue(newValue);
6232
+ tryCommitValue(newValue);
6233
+ // Update edit text to reflect the new value so the user sees the change
6234
+ setEditText(formatValue(newValue));
6122
6235
  }
6123
- // Skip Enter — it's handled in keyDown before Fluent's internal commit
6124
- // clears the raw text and replaces it with the truncated displayValue.
6125
- if (event.key === "Enter") {
6236
+ HandleKeyDown(event);
6237
+ }, [value, step, constrainValue, tryCommitValue, commitEditText, formatValue]);
6238
+ const id = useId("spin-button2");
6239
+ // Real-time validation: when editing, validate the expression; otherwise validate the committed value.
6240
+ // (validateValue already handles NaN, so no separate isNaN check needed.)
6241
+ const isInputInvalid = !validateValue(isEditing ? EvaluateExpression(editText) : value);
6242
+ const mergedClassName = mergeClasses(inputClasses.input, isInputInvalid ? inputClasses.invalid : "", props.className);
6243
+ const inputSlotClassName = mergeClasses(inputClasses.inputSlot, props.inputClassName);
6244
+ const formattedValue = formatValue(value);
6245
+ const handleFocus = useCallback(() => {
6246
+ setIsEditing(true);
6247
+ setEditText(formattedValue);
6248
+ }, [formattedValue]);
6249
+ const handleBlur = useCallback((event) => {
6250
+ // Skip blur handling if a drag just started (icon pointerDown already committed).
6251
+ if (isDragging) {
6126
6252
  return;
6127
6253
  }
6128
- const currVal = evaluateExpression(event.target.value);
6129
- if (!isNaN(currVal)) {
6130
- setValue(currVal);
6131
- tryCommitValue(currVal);
6132
- }
6133
- };
6134
- const id = useId("spin-button");
6135
- const mergedClassName = mergeClasses(classes.input, !validateValue(value) ? classes.invalid : "", props.className);
6136
- // Build input slot from inputClassName
6137
- const inputSlot = {
6138
- className: mergeClasses(classes.inputSlot, props.inputClassName),
6139
- };
6140
- const spinButton = (jsx(SpinButton$1, { ref: ref, ...props, appearance: "outline", input: inputSlot, step: step, id: id, size: size, precision: fluentPrecision, displayValue: `${value.toFixed(displayPrecision)}${props.unit ? " " + props.unit : ""}`, value: value, onChange: handleChange, onKeyDown: handleKeyDown, onKeyUp: handleKeyUp, onBlur: HandleOnBlur, className: mergedClassName }));
6141
- return props.infoLabel ? (jsxs("div", { className: classes.container, children: [jsx(InfoLabel, { ...props.infoLabel, htmlFor: id }), spinButton] })) : (spinButton);
6254
+ commitEditText(event.target.value);
6255
+ setIsEditing(false);
6256
+ HandleOnBlur(event);
6257
+ }, [commitEditText, isDragging]);
6258
+ const contentBefore = !props.disableDragButton && (isHovered || isDragging) && !isInputInvalid ? (jsx(ArrowBidirectionalUpDownFilled, { className: classes.icon, style: { cursor: isDragging ? "ns-resize" : "pointer" }, onPointerDown: handleIconPointerDown, onPointerMove: handleIconPointerMove, onPointerUp: handleIconPointerUp })) : undefined;
6259
+ const input = (jsx("div", { onMouseEnter: () => setIsHovered(true), onMouseLeave: () => {
6260
+ if (!isDragging) {
6261
+ setIsHovered(false);
6262
+ }
6263
+ }, children: jsx(Input, { ref: mergedRef, id: id, appearance: "outline", size: size, className: mergedClassName, input: { className: inputSlotClassName }, value: isEditing ? editText : formattedValue, onChange: handleInputChange, onFocus: handleFocus, onKeyDown: handleKeyDown, onBlur: handleBlur, contentBefore: contentBefore, contentAfter: props.unit }) }));
6264
+ return props.infoLabel ? (jsxs("div", { className: inputClasses.container, children: [jsx(InfoLabel, { ...props.infoLabel, htmlFor: id }), input] })) : (input);
6142
6265
  });
6143
6266
 
6144
6267
  const TextInput = (props) => {
@@ -6820,7 +6943,7 @@ const StatsServiceDefinition = {
6820
6943
  horizontalLocation: "right",
6821
6944
  verticalLocation: "top",
6822
6945
  order: 300,
6823
- suppressTeachingMoment: true,
6946
+ teachingMoment: false,
6824
6947
  content: () => {
6825
6948
  const sections = useOrderedObservableCollection(sectionsCollection);
6826
6949
  const sectionContent = useObservableCollection(sectionContentCollection);
@@ -6909,7 +7032,7 @@ const ToolsServiceDefinition = {
6909
7032
  horizontalLocation: "right",
6910
7033
  verticalLocation: "top",
6911
7034
  order: 400,
6912
- suppressTeachingMoment: true,
7035
+ teachingMoment: false,
6913
7036
  content: () => {
6914
7037
  const sections = useOrderedObservableCollection(sectionsCollection);
6915
7038
  const sectionContent = useObservableCollection(sectionContentCollection);
@@ -6927,12 +7050,337 @@ const ToolsServiceDefinition = {
6927
7050
  },
6928
7051
  };
6929
7052
 
7053
+ const useStyles$F = makeStyles({
7054
+ dropdown: {
7055
+ ...UniformWidthStyling,
7056
+ },
7057
+ });
7058
+ /**
7059
+ * Wraps a dropdown in a property line
7060
+ * @param props - PropertyLineProps and DropdownProps
7061
+ * @returns property-line wrapped dropdown
7062
+ */
7063
+ const DropdownPropertyLine = forwardRef((props, ref) => {
7064
+ DropdownPropertyLine.displayName = "DropdownPropertyLine";
7065
+ const classes = useStyles$F();
7066
+ return (jsx(PropertyLine, { ...props, ref: ref, children: jsx(Dropdown, { ...props, className: classes.dropdown }) }));
7067
+ });
7068
+ /**
7069
+ * Dropdown component for number values.
7070
+ */
7071
+ const NumberDropdownPropertyLine = DropdownPropertyLine;
7072
+ /**
7073
+ * Dropdown component for string values
7074
+ */
7075
+ const StringDropdownPropertyLine = DropdownPropertyLine;
7076
+
7077
+ /**
7078
+ * A slider primitive that wraps the Fluent UI Slider with step scaling, drag tracking, and optional notify-on-release behavior.
7079
+ * Follows the same pattern as other primitives (e.g. Switch) — no wrapper divs, just the Fluent component with logic.
7080
+ * @param props
7081
+ * @returns Slider component
7082
+ */
7083
+ const Slider = (props) => {
7084
+ Slider.displayName = "Slider";
7085
+ const { size } = useContext(ToolContext);
7086
+ const [value, setValue] = useState(props.value ?? 0);
7087
+ const pendingValueRef = useRef(undefined);
7088
+ const isDraggingRef = useRef(false);
7089
+ // NOTE: The Fluent slider will add tick marks if the step prop is anything other than undefined.
7090
+ // To avoid this, we scale the min/max based on the step so we can always make step undefined.
7091
+ // The actual step size in the Fluent slider is 1 when it is set to undefined.
7092
+ const min = props.min ?? 0;
7093
+ const max = props.max ?? 100;
7094
+ const step = props.step ?? 1;
7095
+ useEffect(() => {
7096
+ !isDraggingRef.current && setValue(props.value ?? 0); // Update local state when props.value changes as long as user is not actively dragging
7097
+ }, [props.value]);
7098
+ const handleSliderChange = (_, data) => {
7099
+ const newValue = data.value * step;
7100
+ setValue(newValue);
7101
+ if (props.notifyOnlyOnRelease) {
7102
+ // Store the value but don't notify parent yet
7103
+ pendingValueRef.current = newValue;
7104
+ }
7105
+ else {
7106
+ // Notify parent as slider changes
7107
+ props.onChange(newValue);
7108
+ }
7109
+ };
7110
+ const handleSliderPointerDown = () => {
7111
+ isDraggingRef.current = true;
7112
+ };
7113
+ const handleSliderPointerUp = () => {
7114
+ if (props.notifyOnlyOnRelease && isDraggingRef.current && pendingValueRef.current !== undefined) {
7115
+ props.onChange(pendingValueRef.current);
7116
+ pendingValueRef.current = undefined;
7117
+ }
7118
+ isDraggingRef.current = false;
7119
+ };
7120
+ return (jsx(Slider$1, { className: props.className, size: size, min: min / step, max: max / step, step: undefined, value: value / step, disabled: props.disabled, onChange: handleSliderChange, onPointerDown: () => {
7121
+ handleSliderPointerDown();
7122
+ props.onPointerDown?.();
7123
+ }, onPointerUp: () => {
7124
+ handleSliderPointerUp();
7125
+ props.onPointerUp?.();
7126
+ } }));
7127
+ };
7128
+
7129
+ const useSyncedSliderStyles = makeStyles({
7130
+ container: { display: "flex", minWidth: 0 },
7131
+ syncedSlider: {
7132
+ flex: "1 1 0",
7133
+ flexDirection: "row",
7134
+ display: "flex",
7135
+ alignItems: "center",
7136
+ minWidth: 0,
7137
+ },
7138
+ slider: {
7139
+ flex: "1 1 auto",
7140
+ minWidth: "75px",
7141
+ maxWidth: "75px",
7142
+ },
7143
+ compactSlider: {
7144
+ flex: "1 1 auto",
7145
+ minWidth: "50px", // Allow shrinking for compact mode
7146
+ maxWidth: "75px",
7147
+ },
7148
+ growSlider: {
7149
+ flex: "1 1 auto",
7150
+ minWidth: "50px",
7151
+ // No maxWidth - slider grows to fill available space
7152
+ },
7153
+ compactSpinButton: {
7154
+ width: "65px",
7155
+ minWidth: "65px",
7156
+ maxWidth: "65px",
7157
+ },
7158
+ compactSpinButtonInput: {
7159
+ minWidth: "0",
7160
+ },
7161
+ });
7162
+ /**
7163
+ * Component which synchronizes a slider and an input field, allowing the user to change the value using either control
7164
+ * @param props
7165
+ * @returns SyncedSlider component
7166
+ */
7167
+ const SyncedSliderInput = (props) => {
7168
+ SyncedSliderInput.displayName = "SyncedSliderInput";
7169
+ const { infoLabel, ...passthroughProps } = props;
7170
+ const classes = useSyncedSliderStyles();
7171
+ const [value, setValue] = useState(props.value ?? 0);
7172
+ const pendingValueRef = useRef(undefined);
7173
+ const isDraggingRef = useRef(false);
7174
+ useEffect(() => {
7175
+ !isDraggingRef.current && setValue(props.value ?? 0); // Update local state when props.value changes as long as user is not actively dragging
7176
+ }, [props.value]);
7177
+ const handleSliderChange = (newValue) => {
7178
+ setValue(newValue);
7179
+ if (props.notifyOnlyOnRelease) {
7180
+ // Store the value but don't notify parent yet
7181
+ pendingValueRef.current = newValue;
7182
+ }
7183
+ else {
7184
+ // Notify parent as slider changes
7185
+ props.onChange(newValue);
7186
+ }
7187
+ };
7188
+ const handleSliderPointerDown = () => {
7189
+ isDraggingRef.current = true;
7190
+ };
7191
+ const handleSliderPointerUp = () => {
7192
+ if (props.notifyOnlyOnRelease && isDraggingRef.current && pendingValueRef.current !== undefined) {
7193
+ props.onChange(pendingValueRef.current);
7194
+ pendingValueRef.current = undefined;
7195
+ }
7196
+ isDraggingRef.current = false;
7197
+ };
7198
+ const handleInputChange = (value) => {
7199
+ setValue(value);
7200
+ props.onChange(value); // Input always updates immediately
7201
+ };
7202
+ const hasSlider = props.min !== undefined && props.max !== undefined;
7203
+ // Determine Slider className based on props
7204
+ const getSliderClassName = () => {
7205
+ if (props.growSlider) {
7206
+ return classes.growSlider;
7207
+ }
7208
+ if (props.compact) {
7209
+ return classes.compactSlider;
7210
+ }
7211
+ return classes.slider;
7212
+ };
7213
+ return (jsxs("div", { className: classes.container, children: [infoLabel && jsx(InfoLabel, { ...infoLabel, htmlFor: "syncedSlider" }), jsxs("div", { id: "syncedSlider", className: classes.syncedSlider, children: [hasSlider && (jsx(Slider, { className: getSliderClassName(), value: value, onChange: handleSliderChange, min: props.min, max: props.max, step: props.step, disabled: props.disabled, onPointerDown: handleSliderPointerDown, onPointerUp: handleSliderPointerUp })), jsx(SpinButton, { ...passthroughProps, className: hasSlider || props.compact ? classes.compactSpinButton : undefined, inputClassName: hasSlider || props.compact ? classes.compactSpinButtonInput : undefined, value: value, onChange: handleInputChange, step: props.step, disableDragButton: true })] })] }));
7214
+ };
7215
+
7216
+ /**
7217
+ * Renders a simple wrapper around the SyncedSliderInput
7218
+ * @param props
7219
+ * @returns
7220
+ */
7221
+ const SyncedSliderPropertyLine = forwardRef((props, ref) => {
7222
+ SyncedSliderPropertyLine.displayName = "SyncedSliderPropertyLine";
7223
+ const { label, description, ...sliderProps } = props;
7224
+ return (jsx(PropertyLine, { ref: ref, ...props, children: jsx(SyncedSliderInput, { ...sliderProps }) }));
7225
+ });
7226
+
7227
+ const WatcherSettingDescriptor = {
7228
+ key: "WatcherSettings",
7229
+ defaultValue: {
7230
+ mode: "intercept",
7231
+ },
7232
+ };
7233
+ const WatcherServiceIdentity = Symbol("WatcherService");
7234
+ const WatcherServiceDefinition = {
7235
+ friendlyName: "Watcher Service",
7236
+ produces: [WatcherServiceIdentity],
7237
+ consumes: [SettingsStoreIdentity],
7238
+ factory: (settingsStore) => {
7239
+ let refreshObservable = null;
7240
+ let pollingHandle = null;
7241
+ const applySettings = () => {
7242
+ const settings = settingsStore.readSetting(WatcherSettingDescriptor);
7243
+ if (pollingHandle !== null) {
7244
+ clearInterval(pollingHandle);
7245
+ pollingHandle = null;
7246
+ }
7247
+ if (settings.mode === "intercept") {
7248
+ if (refreshObservable) {
7249
+ refreshObservable.clear();
7250
+ refreshObservable = null;
7251
+ }
7252
+ }
7253
+ else {
7254
+ const pollingObservable = refreshObservable ?? (refreshObservable = new Observable());
7255
+ if (settings.mode === "polling") {
7256
+ pollingHandle = window.setInterval(() => {
7257
+ pollingObservable.notifyObservers();
7258
+ }, settings.interval);
7259
+ }
7260
+ }
7261
+ };
7262
+ const settingsStoreObserver = settingsStore.onChanged.add((key) => {
7263
+ if (key === WatcherSettingDescriptor.key) {
7264
+ applySettings();
7265
+ }
7266
+ });
7267
+ applySettings();
7268
+ return {
7269
+ watchProperty(target, propertyKey, onChanged) {
7270
+ if (refreshObservable) {
7271
+ let previousValue = target[propertyKey];
7272
+ const observer = refreshObservable.add(() => {
7273
+ const currentValue = target[propertyKey];
7274
+ if (!Object.is(previousValue, currentValue)) {
7275
+ previousValue = currentValue;
7276
+ onChanged(currentValue);
7277
+ }
7278
+ });
7279
+ return {
7280
+ dispose: () => observer.remove(),
7281
+ };
7282
+ }
7283
+ else {
7284
+ return InterceptProperty(target, propertyKey, {
7285
+ afterSet: (value) => onChanged(value),
7286
+ });
7287
+ }
7288
+ },
7289
+ refresh: () => {
7290
+ refreshObservable?.notifyObservers();
7291
+ },
7292
+ dispose: () => {
7293
+ if (pollingHandle !== null) {
7294
+ clearInterval(pollingHandle);
7295
+ pollingHandle = null;
7296
+ }
7297
+ refreshObservable?.clear();
7298
+ refreshObservable = null;
7299
+ settingsStoreObserver.remove();
7300
+ },
7301
+ };
7302
+ },
7303
+ };
7304
+ const WatchModes = [
7305
+ { label: "Interception", value: "intercept" },
7306
+ { label: "Polling", value: "polling" },
7307
+ { label: "Manual", value: "manual" },
7308
+ ];
7309
+ const WatcherSettingsServiceDefinition = {
7310
+ friendlyName: "Watcher Settings Service",
7311
+ consumes: [SettingsServiceIdentity],
7312
+ factory: (settingsService) => {
7313
+ const settingsRegistration = settingsService.addSectionContent({
7314
+ key: "watcherSettings",
7315
+ section: "UI",
7316
+ component: () => {
7317
+ const [watcherSettings, setWatcherSettings] = useSetting(WatcherSettingDescriptor);
7318
+ return (jsxs(Fragment, { children: [jsx(DropdownPropertyLine, { label: "Property Watch Mode", description: `Specifies how Inspector watches entity properties for changes. "Interception" sees changes instantly, but for complex scenes can impact performance. "Polling" has less performance impact on complex scenes, but changes are only detected at the specified interval. "Manual" requires the "Refresh" button in the toolbar to be pressed.`, options: WatchModes, value: watcherSettings.mode, onChange: (value) => setWatcherSettings((prev) => {
7319
+ return { interval: 250, ...prev, mode: value };
7320
+ }) }), jsx(Collapse, { visible: watcherSettings.mode === "polling", children: jsx(SyncedSliderPropertyLine, { label: "Polling Interval", description: "A smaller polling interval will detect changes faster but may impact performance more.", min: 30, max: 1000, step: 10, unit: "ms", value: watcherSettings.mode === "polling" ? watcherSettings.interval : NaN, onChange: (value) => setWatcherSettings((prev) => {
7321
+ return { ...prev, interval: value };
7322
+ }) }) })] }));
7323
+ },
7324
+ });
7325
+ return {
7326
+ dispose: () => {
7327
+ settingsRegistration.dispose();
7328
+ },
7329
+ };
7330
+ },
7331
+ };
7332
+ const WatcherRefreshToolbarServiceDefinition = {
7333
+ friendlyName: "Watcher Refresh Toolbar Service",
7334
+ consumes: [WatcherServiceIdentity, SettingsStoreIdentity, ShellServiceIdentity],
7335
+ factory: (watcherService, settingsStore, shellService) => {
7336
+ let toolbarItemRegistration = null;
7337
+ const updateToolbar = () => {
7338
+ const settings = settingsStore.readSetting(WatcherSettingDescriptor);
7339
+ if (settings.mode === "manual") {
7340
+ if (!toolbarItemRegistration) {
7341
+ toolbarItemRegistration = shellService.addToolbarItem({
7342
+ key: "Watcher Refresh",
7343
+ displayName: "Refresh Properties",
7344
+ verticalLocation: "bottom",
7345
+ horizontalLocation: "right",
7346
+ order: 200 /* DefaultToolbarItemOrder.RefreshProperties */,
7347
+ teachingMoment: {
7348
+ title: "Refresh Properties",
7349
+ description: "Press this button to manually refresh all UI bound to scene state. This is only available when Property Watch Mode is set to Manual in the settings pane.",
7350
+ },
7351
+ component: () => {
7352
+ return (jsx(Button, { appearance: "subtle", icon: ArrowClockwiseRegular, title: "Update all UI (e.g. Scene Explorer, Properties, etc.) bound to properties of entities (Meshes, Materials, etc.)", onClick: () => watcherService.refresh() }));
7353
+ },
7354
+ });
7355
+ }
7356
+ }
7357
+ else {
7358
+ toolbarItemRegistration?.dispose();
7359
+ toolbarItemRegistration = null;
7360
+ }
7361
+ };
7362
+ updateToolbar();
7363
+ const settingsStoreObserver = settingsStore.onChanged.add((key) => {
7364
+ if (key === WatcherSettingDescriptor.key) {
7365
+ updateToolbar();
7366
+ }
7367
+ });
7368
+ return {
7369
+ dispose: () => {
7370
+ toolbarItemRegistration?.dispose();
7371
+ toolbarItemRegistration = null;
7372
+ settingsStoreObserver.remove();
7373
+ },
7374
+ };
7375
+ },
7376
+ };
7377
+
6930
7378
  const GizmoServiceIdentity = Symbol("GizmoService");
6931
7379
  const GizmoServiceDefinition = {
6932
7380
  friendlyName: "Gizmo Service",
6933
7381
  produces: [GizmoServiceIdentity],
6934
- consumes: [SceneContextIdentity, SelectionServiceIdentity],
6935
- factory: (sceneContext, selectionService) => {
7382
+ consumes: [SceneContextIdentity, SelectionServiceIdentity, WatcherServiceIdentity],
7383
+ factory: (sceneContext, selectionService, watcherService) => {
6936
7384
  // Ref-counted utility layers, shared across consumers.
6937
7385
  const utilityLayers = new WeakMap();
6938
7386
  const getUtilityLayer = (scene, layer = "default") => {
@@ -7031,13 +7479,11 @@ const GizmoServiceDefinition = {
7031
7479
  currentKeepDepthUtilityLayerRef = null;
7032
7480
  };
7033
7481
  gm.coordinatesMode = coordinatesModeState;
7034
- coordinatesModeInterceptToken = InterceptProperty(gm, "coordinatesMode", {
7035
- afterSet: (value) => {
7036
- if (value !== coordinatesModeState) {
7037
- coordinatesModeState = value;
7038
- coordinatesModeObservable.notifyObservers();
7039
- }
7040
- },
7482
+ coordinatesModeInterceptToken = watcherService.watchProperty(gm, "coordinatesMode", (value) => {
7483
+ if (value !== coordinatesModeState) {
7484
+ coordinatesModeState = value;
7485
+ coordinatesModeObservable.notifyObservers();
7486
+ }
7041
7487
  });
7042
7488
  currentGizmoManager = gm;
7043
7489
  }
@@ -7210,7 +7656,7 @@ const DefaultInspectorExtensionFeed = new BuiltInsExtensionFeed("Inspector", [
7210
7656
  keywords: ["creation", "tools"],
7211
7657
  ...BabylonWebResources,
7212
7658
  author: { name: "Babylon.js", forumUserName: "" },
7213
- getExtensionModuleAsync: async () => await import('./quickCreateToolsService-C9jZpIAl.js'),
7659
+ getExtensionModuleAsync: async () => await import('./quickCreateToolsService-DeZ7ZJOC.js'),
7214
7660
  },
7215
7661
  {
7216
7662
  name: "Reflector",
@@ -7218,116 +7664,10 @@ const DefaultInspectorExtensionFeed = new BuiltInsExtensionFeed("Inspector", [
7218
7664
  keywords: ["reflector", "bridge", "sync", "sandbox", "tools"],
7219
7665
  ...BabylonWebResources,
7220
7666
  author: { name: "Babylon.js", forumUserName: "" },
7221
- getExtensionModuleAsync: async () => await import('./reflectorService-DGy36OAK.js'),
7667
+ getExtensionModuleAsync: async () => await import('./reflectorService-DE0Ic3N5.js'),
7222
7668
  },
7223
7669
  ]);
7224
7670
 
7225
- const useSyncedSliderStyles = makeStyles({
7226
- container: { display: "flex", minWidth: 0 },
7227
- syncedSlider: {
7228
- flex: "1 1 0",
7229
- flexDirection: "row",
7230
- display: "flex",
7231
- alignItems: "center",
7232
- minWidth: 0,
7233
- },
7234
- slider: {
7235
- flex: "1 1 auto",
7236
- minWidth: "75px",
7237
- maxWidth: "75px",
7238
- },
7239
- compactSlider: {
7240
- flex: "1 1 auto",
7241
- minWidth: "50px", // Allow shrinking for compact mode
7242
- maxWidth: "75px",
7243
- },
7244
- growSlider: {
7245
- flex: "1 1 auto",
7246
- minWidth: "50px",
7247
- // No maxWidth - slider grows to fill available space
7248
- },
7249
- compactSpinButton: {
7250
- width: "65px",
7251
- minWidth: "65px",
7252
- maxWidth: "65px",
7253
- },
7254
- compactSpinButtonInput: {
7255
- minWidth: "0",
7256
- },
7257
- });
7258
- /**
7259
- * Component which synchronizes a slider and an input field, allowing the user to change the value using either control
7260
- * @param props
7261
- * @returns SyncedSlider component
7262
- */
7263
- const SyncedSliderInput = (props) => {
7264
- SyncedSliderInput.displayName = "SyncedSliderInput";
7265
- const { infoLabel, ...passthroughProps } = props;
7266
- const classes = useSyncedSliderStyles();
7267
- const { size } = useContext(ToolContext);
7268
- const [value, setValue] = useState(props.value ?? 0);
7269
- const pendingValueRef = useRef(undefined);
7270
- const isDraggingRef = useRef(false);
7271
- // NOTE: The Fluent slider will add tick marks if the step prop is anything other than undefined.
7272
- // To avoid this, we scale the min/max based on the step so we can always make step undefined.
7273
- // The actual step size in the Fluent slider is 1 when it is ste to undefined.
7274
- const min = props.min ?? 0;
7275
- const max = props.max ?? 100;
7276
- const step = props.step ?? 1;
7277
- useEffect(() => {
7278
- !isDraggingRef.current && setValue(props.value ?? 0); // Update local state when props.value changes as long as user is not actively dragging
7279
- }, [props.value]);
7280
- const handleSliderChange = (_, data) => {
7281
- const newValue = data.value * step;
7282
- setValue(newValue);
7283
- if (props.notifyOnlyOnRelease) {
7284
- // Store the value but don't notify parent yet
7285
- pendingValueRef.current = newValue;
7286
- }
7287
- else {
7288
- // Notify parent as slider changes
7289
- props.onChange(newValue);
7290
- }
7291
- };
7292
- const handleSliderPointerDown = () => {
7293
- isDraggingRef.current = true;
7294
- };
7295
- const handleSliderPointerUp = () => {
7296
- if (props.notifyOnlyOnRelease && isDraggingRef.current && pendingValueRef.current !== undefined) {
7297
- props.onChange(pendingValueRef.current);
7298
- pendingValueRef.current = undefined;
7299
- }
7300
- isDraggingRef.current = false;
7301
- };
7302
- const handleInputChange = (value) => {
7303
- setValue(value);
7304
- props.onChange(value); // Input always updates immediately
7305
- };
7306
- const hasSlider = props.min !== undefined && props.max !== undefined;
7307
- // Determine Slider className based on props
7308
- const getSliderClassName = () => {
7309
- if (props.growSlider) {
7310
- return classes.growSlider;
7311
- }
7312
- if (props.compact) {
7313
- return classes.compactSlider;
7314
- }
7315
- return classes.slider;
7316
- };
7317
- return (jsxs("div", { className: classes.container, children: [infoLabel && jsx(InfoLabel, { ...infoLabel, htmlFor: "syncedSlider" }), jsxs("div", { id: "syncedSlider", className: classes.syncedSlider, children: [hasSlider && (jsx(Slider, { ...passthroughProps, className: getSliderClassName(), size: size, min: min / step, max: max / step, step: undefined, value: value / step, onChange: handleSliderChange, onPointerDown: handleSliderPointerDown, onPointerUp: handleSliderPointerUp })), jsx(SpinButton, { ...passthroughProps, className: hasSlider || props.compact ? classes.compactSpinButton : undefined, inputClassName: hasSlider || props.compact ? classes.compactSpinButtonInput : undefined, value: value, onChange: handleInputChange, step: props.step })] })] }));
7318
- };
7319
-
7320
- /**
7321
- * Renders a simple wrapper around the SyncedSliderInput
7322
- * @param props
7323
- * @returns
7324
- */
7325
- const SyncedSliderPropertyLine = forwardRef((props, ref) => {
7326
- SyncedSliderPropertyLine.displayName = "SyncedSliderPropertyLine";
7327
- const { label, description, ...sliderProps } = props;
7328
- return (jsx(PropertyLine, { ref: ref, ...props, children: jsx(SyncedSliderInput, { ...sliderProps }) }));
7329
- });
7330
-
7331
7671
  /**
7332
7672
  * Reusable component which renders a color property line containing a label, colorPicker popout, and expandable RGBA values
7333
7673
  * The expandable RGBA values are synced sliders that allow the user to modify the color's RGBA values directly
@@ -7362,30 +7702,6 @@ const ColorSliders = ({ color, onSliderChange }) => (jsxs(Fragment, { children:
7362
7702
  const Color3PropertyLine = ColorPropertyLine;
7363
7703
  const Color4PropertyLine = ColorPropertyLine;
7364
7704
 
7365
- const useStyles$F = makeStyles({
7366
- dropdown: {
7367
- ...UniformWidthStyling,
7368
- },
7369
- });
7370
- /**
7371
- * Wraps a dropdown in a property line
7372
- * @param props - PropertyLineProps and DropdownProps
7373
- * @returns property-line wrapped dropdown
7374
- */
7375
- const DropdownPropertyLine = forwardRef((props, ref) => {
7376
- DropdownPropertyLine.displayName = "DropdownPropertyLine";
7377
- const classes = useStyles$F();
7378
- return (jsx(PropertyLine, { ...props, ref: ref, children: jsx(Dropdown, { ...props, className: classes.dropdown }) }));
7379
- });
7380
- /**
7381
- * Dropdown component for number values.
7382
- */
7383
- const NumberDropdownPropertyLine = DropdownPropertyLine;
7384
- /**
7385
- * Dropdown component for string values
7386
- */
7387
- const StringDropdownPropertyLine = DropdownPropertyLine;
7388
-
7389
7705
  /**
7390
7706
  * Wraps a text input in a property line
7391
7707
  * @param props - PropertyLineProps and InputProps
@@ -7430,21 +7746,21 @@ const TensorPropertyLine = (props) => {
7430
7746
  useEffect(() => {
7431
7747
  setVector(props.value);
7432
7748
  }, [props.value, props.expandedContent]);
7433
- return (jsx(PropertyLine, { ...props, expandedContent: vector ? jsx(VectorSliders, { vector: vector, min: min, max: max, unit: props.unit, step: props.step, converted: converted, onChange: onChange }) : undefined, children: jsx(Body1, { children: `[${formatted(props.value.x)}, ${formatted(props.value.y)}${HasZ(props.value) ? `, ${formatted(props.value.z)}` : ""}${HasW(props.value) ? `, ${formatted(props.value.w)}` : ""}]` }) }));
7749
+ return (jsx(PropertyLine, { ...props, expandedContent: jsxs(Fragment, { children: [props.expandedContent, vector ? (jsx(VectorSliders, { vector: vector, min: min, max: max, unit: props.unit, step: props.step, precision: props.precision, converted: converted, onChange: onChange })) : undefined] }), children: jsx(Body1, { children: `[${formatted(props.value.x)}, ${formatted(props.value.y)}${HasZ(props.value) ? `, ${formatted(props.value.z)}` : ""}${HasW(props.value) ? `, ${formatted(props.value.w)}` : ""}]` }) }));
7434
7750
  };
7435
- const VectorSliders = ({ vector, min, max, unit, step, converted, onChange }) => (jsxs(Fragment, { children: [jsx(SyncedSliderPropertyLine, { label: "X", value: converted(vector.x), min: min, max: max, onChange: (val) => onChange(val, "x"), unit: unit, step: step }), jsx(SyncedSliderPropertyLine, { label: "Y", value: converted(vector.y), min: min, max: max, onChange: (val) => onChange(val, "y"), unit: unit, step: step }), HasZ(vector) && jsx(SyncedSliderPropertyLine, { label: "Z", value: converted(vector.z), min: min, max: max, onChange: (val) => onChange(val, "z"), unit: unit, step: step }), HasW(vector) && jsx(SyncedSliderPropertyLine, { label: "W", value: converted(vector.w), min: min, max: max, onChange: (val) => onChange(val, "w"), unit: unit, step: step })] }));
7751
+ const VectorSliders = ({ vector, min, max, unit, step, precision, converted, onChange }) => (jsxs(Fragment, { children: [jsx(NumberInputPropertyLine, { label: "X", value: converted(vector.x), min: min, max: max, onChange: (val) => onChange(val, "x"), unit: unit, step: step, precision: precision }), jsx(NumberInputPropertyLine, { label: "Y", value: converted(vector.y), min: min, max: max, onChange: (val) => onChange(val, "y"), unit: unit, step: step, precision: precision }), HasZ(vector) && (jsx(NumberInputPropertyLine, { label: "Z", value: converted(vector.z), min: min, max: max, onChange: (val) => onChange(val, "z"), unit: unit, step: step, precision: precision })), HasW(vector) && (jsx(NumberInputPropertyLine, { label: "W", value: converted(vector.w), min: min, max: max, onChange: (val) => onChange(val, "w"), unit: unit, step: step, precision: precision }))] }));
7436
7752
  const ToDegreesConverter = { from: Tools.ToDegrees, to: Tools.ToRadians };
7437
7753
  const RotationVectorPropertyLine = (props) => {
7438
7754
  RotationVectorPropertyLine.displayName = "RotationVectorPropertyLine";
7439
- const min = props.useDegrees ? 0 : undefined;
7440
- const max = props.useDegrees ? 360 : undefined;
7441
- return (jsx(Vector3PropertyLine, { ...props, unit: props.useDegrees ? "deg" : "rad", valueConverter: props.useDegrees ? ToDegreesConverter : undefined, min: min, max: max, step: 0.001 }));
7755
+ const step = props.useDegrees ? 1 : 0.01;
7756
+ const precision = props.useDegrees ? 1 : 2;
7757
+ return (jsx(Vector3PropertyLine, { ...props, unit: props.useDegrees ? "°" : "rad", valueConverter: props.useDegrees ? ToDegreesConverter : undefined, step: step, precision: precision }));
7442
7758
  };
7443
7759
  const QuaternionPropertyLineInternal = TensorPropertyLine;
7444
7760
  const QuaternionPropertyLine = (props) => {
7445
7761
  QuaternionPropertyLine.displayName = "QuaternionPropertyLine";
7446
- const min = props.useDegrees ? 0 : undefined;
7447
- const max = props.useDegrees ? 360 : undefined;
7762
+ const step = props.useDegrees ? 1 : 0.01;
7763
+ const precision = props.useDegrees ? 1 : 2;
7448
7764
  const [quat, setQuat] = useState(props.value);
7449
7765
  useEffect(() => {
7450
7766
  setQuat(props.value);
@@ -7459,7 +7775,7 @@ const QuaternionPropertyLine = (props) => {
7459
7775
  const quat = Quaternion.FromEulerAngles(val.x, val.y, val.z);
7460
7776
  onQuatChange(quat);
7461
7777
  };
7462
- return useEuler ? (jsx(Vector3PropertyLine, { ...restProps, nullable: false, ignoreNullable: false, value: quat.toEulerAngles(), valueConverter: ToDegreesConverter, min: min, max: max, onChange: onEulerChange, unit: props.useDegrees ? "deg" : "rad" })) : (jsx(QuaternionPropertyLineInternal, { ...props, nullable: false, value: quat, min: min, max: max, onChange: onQuatChange, unit: props.useDegrees ? "deg" : "rad" }));
7778
+ return useEuler ? (jsx(Vector3PropertyLine, { ...restProps, nullable: false, ignoreNullable: false, value: quat.toEulerAngles(), valueConverter: ToDegreesConverter, onChange: onEulerChange, unit: props.useDegrees ? "°" : "rad", step: step, precision: precision, expandedContent: jsx(TextPropertyLine, { label: "Quaternion", value: `[${quat.x.toFixed(4)}, ${quat.y.toFixed(4)}, ${quat.z.toFixed(4)}, ${quat.w.toFixed(4)}]` }) })) : (jsx(QuaternionPropertyLineInternal, { ...props, nullable: false, value: quat, onChange: onQuatChange, unit: props.useDegrees ? "°" : "rad", step: step, precision: precision }));
7463
7779
  };
7464
7780
  const Vector2PropertyLine = TensorPropertyLine;
7465
7781
  const Vector3PropertyLine = TensorPropertyLine;
@@ -7974,7 +8290,7 @@ const ThemeSelectorServiceDefinition = {
7974
8290
  key: "ThemeSelector",
7975
8291
  horizontalLocation: "right",
7976
8292
  verticalLocation: "top",
7977
- suppressTeachingMoment: true,
8293
+ teachingMoment: false,
7978
8294
  order: -300,
7979
8295
  component: () => {
7980
8296
  const classes = useStyles$E();
@@ -8039,7 +8355,7 @@ function MakeModularTool(options) {
8039
8355
  const [requiredExtensions, setRequiredExtensions] = useState();
8040
8356
  const [requiredExtensionsDeferred, setRequiredExtensionsDeferred] = useState();
8041
8357
  const [extensionInstallError, setExtensionInstallError] = useState();
8042
- const [rootComponent, setRootComponent] = useState();
8358
+ const [bootstrapServices, setBootstrapServices] = useState();
8043
8359
  // This is the main async initialization.
8044
8360
  useEffect(() => {
8045
8361
  const initializeExtensionManagerAsync = async () => {
@@ -8050,17 +8366,22 @@ function MakeModularTool(options) {
8050
8366
  produces: [SettingsStoreIdentity],
8051
8367
  factory: () => settingsStore,
8052
8368
  });
8369
+ // Register watcher service early since many other services will rely on it.
8370
+ // TODO: Really this should be in the Inspector layer, but we would need a way
8371
+ // to setup the WatcherContext.Provider before the root component is rendered
8372
+ // for that to work, since components will use the WatcherContext.
8373
+ await serviceContainer.addServiceAsync(WatcherServiceDefinition);
8053
8374
  // Register the shell service (top level toolbar/side pane UI layout).
8054
8375
  await serviceContainer.addServiceAsync(MakeShellServiceDefinition(options));
8055
- // Register a service that simply consumes the IRootComponentService and sets the root component as state so it can be rendered.
8376
+ // Register a service that simply consumes the services we need before first render.
8056
8377
  await serviceContainer.addServiceAsync({
8057
- friendlyName: "Root Component Bootstrapper",
8058
- consumes: [RootComponentServiceIdentity],
8059
- factory: (rootComponentService) => {
8378
+ friendlyName: "Service Bootstrapper",
8379
+ consumes: [RootComponentServiceIdentity, WatcherServiceIdentity],
8380
+ factory: (rootComponentService, watcherService) => {
8060
8381
  // Use function syntax for the state setter since the root component may be a function component.
8061
- setRootComponent(() => rootComponentService.rootComponent);
8382
+ setBootstrapServices({ rootComponentService, watcherService });
8062
8383
  return {
8063
- dispose: () => setRootComponent(undefined),
8384
+ dispose: () => setBootstrapServices(undefined),
8064
8385
  };
8065
8386
  },
8066
8387
  });
@@ -8072,7 +8393,7 @@ function MakeModularTool(options) {
8072
8393
  }
8073
8394
  // Register the extension list service (for browsing/installing extensions) if extension feeds are provided.
8074
8395
  if (extensionFeeds.length > 0) {
8075
- const { ExtensionListServiceDefinition } = await import('./extensionsListService-qU9tqfBg.js');
8396
+ const { ExtensionListServiceDefinition } = await import('./extensionsListService-CAOSDkIg.js');
8076
8397
  await serviceContainer.addServiceAsync(ExtensionListServiceDefinition);
8077
8398
  }
8078
8399
  // Register all external services (that make up a unique tool).
@@ -8139,12 +8460,17 @@ function MakeModularTool(options) {
8139
8460
  setExtensionInstallError(undefined);
8140
8461
  }, [setExtensionInstallError]);
8141
8462
  // Show a spinner until a main view has been set.
8142
- // eslint-disable-next-line @typescript-eslint/naming-convention
8143
- const Content = rootComponent ?? (() => jsx(Spinner, { className: classes.spinner }));
8144
- return (
8145
- // Expose the settings store as a React context so that UI components can read/write
8146
- // settings without the ISettingsService needing to be explicitly passed around.
8147
- jsx(SettingsStoreContext.Provider, { value: settingsStore, children: jsx(ExtensionManagerContext.Provider, { value: extensionManagerContext, children: jsx(Theme, { className: classes.app, children: jsxs(ToastProvider, { children: [jsx(Dialog, { open: !!requiredExtensions, modalType: "alert", children: jsx(DialogSurface, { children: jsxs(DialogBody, { children: [jsx(DialogTitle, { children: "Required Extensions" }), jsxs(DialogContent, { children: ["Opening this URL requires the following extensions to be installed and enabled:", jsx("ul", { children: requiredExtensions?.map((name) => jsx("li", { children: name }, name)) })] }), jsxs(DialogActions, { children: [jsx(Button$1, { appearance: "primary", onClick: onAcceptRequiredExtensions, children: "Install & Enable" }), jsx(Button$1, { appearance: "secondary", onClick: onRejectRequiredExtensions, children: "No Thanks" })] })] }) }) }), jsx(Dialog, { open: !!extensionInstallError, modalType: "alert", children: jsx(DialogSurface, { children: jsxs(DialogBody, { children: [jsx(DialogTitle, { children: jsxs("div", { className: classes.extensionErrorTitleDiv, children: ["Extension Install Error", jsx(ErrorCircleRegular, { className: classes.extensionErrorIcon })] }) }), jsx(DialogContent, { children: jsxs(List$1, { children: [jsx(ListItem, { children: jsx(Body1, { children: `Extension "${extensionInstallError?.extension.name}" failed to install and was removed.` }) }), jsx(ListItem, { children: jsx(Body1, { children: `${extensionInstallError?.error}` }) })] }) }), jsx(DialogActions, { children: jsx(Button$1, { appearance: "primary", onClick: onAcknowledgedExtensionInstallError, children: "Close" }) })] }) }) }), jsx(Suspense, { fallback: jsx(Spinner, { className: classes.spinner }), children: jsx(Content, {}) })] }) }) }) }));
8463
+ if (!bootstrapServices) {
8464
+ return (jsx(SettingsStoreContext.Provider, { value: settingsStore, children: jsx(Theme, { className: classes.app, children: jsx(Spinner, { className: classes.spinner }) }) }));
8465
+ }
8466
+ else {
8467
+ // eslint-disable-next-line @typescript-eslint/naming-convention
8468
+ const Content = bootstrapServices.rootComponentService.rootComponent;
8469
+ return (
8470
+ // Expose the settings store as a React context so that UI components can read/write
8471
+ // settings without the ISettingsService needing to be explicitly passed around.
8472
+ jsx(SettingsStoreContext.Provider, { value: settingsStore, children: jsx(WatcherContext.Provider, { value: bootstrapServices.watcherService, children: jsx(ExtensionManagerContext.Provider, { value: extensionManagerContext, children: jsx(Theme, { className: classes.app, children: jsxs(ToastProvider, { children: [jsx(Dialog, { open: !!requiredExtensions, modalType: "alert", children: jsx(DialogSurface, { children: jsxs(DialogBody, { children: [jsx(DialogTitle, { children: "Required Extensions" }), jsxs(DialogContent, { children: ["Opening this URL requires the following extensions to be installed and enabled:", jsx("ul", { children: requiredExtensions?.map((name) => jsx("li", { children: name }, name)) })] }), jsxs(DialogActions, { children: [jsx(Button$1, { appearance: "primary", onClick: onAcceptRequiredExtensions, children: "Install & Enable" }), jsx(Button$1, { appearance: "secondary", onClick: onRejectRequiredExtensions, children: "No Thanks" })] })] }) }) }), jsx(Dialog, { open: !!extensionInstallError, modalType: "alert", children: jsx(DialogSurface, { children: jsxs(DialogBody, { children: [jsx(DialogTitle, { children: jsxs("div", { className: classes.extensionErrorTitleDiv, children: ["Extension Install Error", jsx(ErrorCircleRegular, { className: classes.extensionErrorIcon })] }) }), jsx(DialogContent, { children: jsxs(List$1, { children: [jsx(ListItem, { children: jsx(Body1, { children: `Extension "${extensionInstallError?.extension.name}" failed to install and was removed.` }) }), jsx(ListItem, { children: jsx(Body1, { children: `${extensionInstallError?.error}` }) })] }) }), jsx(DialogActions, { children: jsx(Button$1, { appearance: "primary", onClick: onAcknowledgedExtensionInstallError, children: "Close" }) })] }) }) }), jsx(Suspense, { fallback: jsx(Spinner, { className: classes.spinner }), children: jsx(Content, {}) })] }) }) }) }) }));
8473
+ }
8148
8474
  };
8149
8475
  // Set the container element to be a flex container so that the tool can be displayed properly.
8150
8476
  const originalContainerElementDisplay = containerElement.style.display;
@@ -8223,12 +8549,103 @@ const GizmoToolbarServiceDefinition = {
8223
8549
  key: "Gizmo Toolbar",
8224
8550
  verticalLocation: "top",
8225
8551
  horizontalLocation: "left",
8226
- suppressTeachingMoment: true,
8552
+ teachingMoment: false,
8227
8553
  component: () => jsx(GizmoToolbar, { gizmoService: gizmoService, sceneContext: sceneContext }),
8228
8554
  });
8229
8555
  },
8230
8556
  };
8231
8557
 
8558
+ const HighlightSelectedEntitySettingDescriptor = {
8559
+ key: "HighlightSelectedEntity",
8560
+ defaultValue: true,
8561
+ };
8562
+ const HighlightServiceDefinition = {
8563
+ friendlyName: "Highlight Service",
8564
+ consumes: [SelectionServiceIdentity, SceneContextIdentity, SettingsStoreIdentity, ThemeServiceIdentity, GizmoServiceIdentity],
8565
+ factory: (selectionService, sceneContext, settingsStore, themeService, gizmoService) => {
8566
+ let outlineLayer = null;
8567
+ let utilityLayer = null;
8568
+ let currentScene = null;
8569
+ let activeCameraObserver = null;
8570
+ function disposeOutlineLayer() {
8571
+ outlineLayer?.dispose();
8572
+ outlineLayer = null;
8573
+ utilityLayer?.dispose();
8574
+ utilityLayer = null;
8575
+ currentScene = null;
8576
+ }
8577
+ function getOrCreateOutlineLayer(scene) {
8578
+ if (!outlineLayer || currentScene !== scene) {
8579
+ disposeOutlineLayer();
8580
+ utilityLayer = gizmoService.getUtilityLayer(scene);
8581
+ outlineLayer = new SelectionOutlineLayer("InspectorSelectionOutline", utilityLayer.value.utilityLayerScene);
8582
+ updateColor(outlineLayer);
8583
+ currentScene = scene;
8584
+ }
8585
+ return outlineLayer;
8586
+ }
8587
+ function updateColor(outlineLayer) {
8588
+ outlineLayer.outlineColor = Color3.FromHexString(themeService.theme.colorBrandForeground1);
8589
+ }
8590
+ function updateHighlight() {
8591
+ const scene = sceneContext.currentScene;
8592
+ const entity = selectionService.selectedEntity instanceof AbstractMesh && !(selectionService.selectedEntity instanceof GaussianSplattingMesh)
8593
+ ? selectionService.selectedEntity
8594
+ : null;
8595
+ if (!entity || !settingsStore.readSetting(HighlightSelectedEntitySettingDescriptor) || !scene || !scene.activeCamera) {
8596
+ disposeOutlineLayer();
8597
+ return;
8598
+ }
8599
+ const layer = getOrCreateOutlineLayer(scene);
8600
+ layer.clearSelection();
8601
+ layer.addSelection(entity);
8602
+ }
8603
+ function watchActiveCamera(scene) {
8604
+ activeCameraObserver?.remove();
8605
+ activeCameraObserver = null;
8606
+ if (scene) {
8607
+ activeCameraObserver = scene.onActiveCameraChanged.add(updateHighlight);
8608
+ }
8609
+ }
8610
+ // React to theme changes.
8611
+ const themeObserver = themeService.onChanged.add(() => {
8612
+ if (outlineLayer) {
8613
+ updateColor(outlineLayer);
8614
+ }
8615
+ });
8616
+ // React to selection changes.
8617
+ const selectionObserver = selectionService.onSelectedEntityChanged.add(updateHighlight);
8618
+ // React to scene changes.
8619
+ const sceneObserver = sceneContext.currentSceneObservable.add(() => {
8620
+ // Dispose the old layer when the scene changes.
8621
+ disposeOutlineLayer();
8622
+ watchActiveCamera(sceneContext.currentScene);
8623
+ updateHighlight();
8624
+ });
8625
+ // React to setting changes.
8626
+ const settingObserver = settingsStore.onChanged.add((setting) => {
8627
+ if (setting === HighlightSelectedEntitySettingDescriptor.key) {
8628
+ updateHighlight();
8629
+ }
8630
+ });
8631
+ // Watch active camera on the initial scene.
8632
+ watchActiveCamera(sceneContext.currentScene);
8633
+ // Initial update.
8634
+ updateHighlight();
8635
+ return {
8636
+ dispose: () => {
8637
+ themeObserver.remove();
8638
+ selectionObserver.remove();
8639
+ sceneObserver.remove();
8640
+ settingObserver.remove();
8641
+ activeCameraObserver?.remove();
8642
+ activeCameraObserver = null;
8643
+ disposeOutlineLayer();
8644
+ },
8645
+ };
8646
+ },
8647
+ };
8648
+
8232
8649
  const useStyles$B = makeStyles({
8233
8650
  badge: {
8234
8651
  margin: tokens.spacingHorizontalXXS,
@@ -8243,7 +8660,8 @@ const MiniStatsServiceDefinition = {
8243
8660
  key: "Mini Stats",
8244
8661
  verticalLocation: "bottom",
8245
8662
  horizontalLocation: "right",
8246
- suppressTeachingMoment: true,
8663
+ order: 300 /* DefaultToolbarItemOrder.FrameRate */,
8664
+ teachingMoment: false,
8247
8665
  component: () => {
8248
8666
  const classes = useStyles$B();
8249
8667
  const scene = useObservableState(useCallback(() => sceneContext.currentScene, [sceneContext.currentScene]), sceneContext.currentSceneObservable);
@@ -12429,7 +12847,7 @@ const SoundCommandProperties = (props) => {
12429
12847
  else {
12430
12848
  sound.play();
12431
12849
  }
12432
- } }), jsx(Property, { component: SyncedSliderPropertyLine, label: "Volume", functionPath: "setVolume", value: volume, min: 0, max: 5, step: 0.1, onChange: (value) => {
12850
+ } }), jsx(Property, { component: NumberInputPropertyLine, label: "Volume", functionPath: "setVolume", value: volume, min: 0, step: 0.1, onChange: (value) => {
12433
12851
  sound.setVolume(value);
12434
12852
  } }), jsx(BoundProperty, { component: SwitchPropertyLine, label: "Loop", target: sound, propertyKey: "loop" })] }));
12435
12853
  };
@@ -12469,7 +12887,7 @@ const ArcRotateCameraTransformProperties = (props) => {
12469
12887
  const upperBetaLimit = useProperty(camera, "upperBetaLimit") ?? Math.PI;
12470
12888
  const lowerRadiusLimit = useProperty(camera, "lowerRadiusLimit");
12471
12889
  const upperRadiusLimit = useProperty(camera, "upperRadiusLimit");
12472
- return (jsxs(Fragment, { children: [jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Alpha", description: `Horizontal angle in ${useDegrees ? "degrees" : "radians"}`, target: camera, propertyKey: "alpha", min: toDisplayAngle(lowerAlphaLimit), max: toDisplayAngle(upperAlphaLimit), step: toDisplayAngle(0.01), convertTo: (value) => toDisplayAngle(value, true), convertFrom: fromDisplayAngle }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Beta", description: `Vertical angle in ${useDegrees ? "degrees" : "radians"}`, target: camera, propertyKey: "beta", min: toDisplayAngle(lowerBetaLimit), max: toDisplayAngle(upperBetaLimit), step: toDisplayAngle(0.01), convertTo: (value) => toDisplayAngle(value, true), convertFrom: fromDisplayAngle }), lowerRadiusLimit != null && upperRadiusLimit != null ? (jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Radius", description: "Distance from the target point.", target: camera, propertyKey: "radius", min: lowerRadiusLimit, max: upperRadiusLimit, step: 0.01 })) : (jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Radius", description: "Distance from the target point.", target: camera, propertyKey: "radius", min: 0, step: 0.01 }))] }));
12890
+ return (jsxs(Fragment, { children: [jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Alpha", description: `Horizontal angle in ${useDegrees ? "degrees" : "radians"}`, target: camera, propertyKey: "alpha", min: toDisplayAngle(lowerAlphaLimit), max: toDisplayAngle(upperAlphaLimit), step: toDisplayAngle(0.01), unit: useDegrees ? "°" : "rad", convertTo: (value) => toDisplayAngle(value, true), convertFrom: fromDisplayAngle }), jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Beta", description: `Vertical angle in ${useDegrees ? "degrees" : "radians"}`, target: camera, propertyKey: "beta", min: toDisplayAngle(lowerBetaLimit), max: toDisplayAngle(upperBetaLimit), step: toDisplayAngle(0.01), unit: useDegrees ? "°" : "rad", convertTo: (value) => toDisplayAngle(value, true), convertFrom: fromDisplayAngle }), jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Radius", description: "Distance from the target point.", target: camera, propertyKey: "radius", min: lowerRadiusLimit ?? undefined, max: upperRadiusLimit ?? undefined, step: 0.01 })] }));
12473
12891
  };
12474
12892
  const ArcRotateCameraControlProperties = (props) => {
12475
12893
  const { camera } = props;
@@ -12481,8 +12899,19 @@ const ArcRotateCameraCollisionProperties = (props) => {
12481
12899
  };
12482
12900
  const ArcRotateCameraLimitsProperties = (props) => {
12483
12901
  const { camera } = props;
12902
+ const [toDisplayAngle, fromDisplayAngle, useDegrees] = useAngleConverters();
12903
+ const minAlphaLimit = 0;
12904
+ const maxAlphaLimit = Math.PI * 2;
12905
+ const minBetaLimit = -Math.PI;
12906
+ const maxBetaLimit = Math.PI;
12907
+ const lowerAlphaLimit = useProperty(camera, "lowerAlphaLimit") ?? minAlphaLimit;
12908
+ const upperAlphaLimit = useProperty(camera, "upperAlphaLimit") ?? maxAlphaLimit;
12909
+ const lowerBetaLimit = useProperty(camera, "lowerBetaLimit") ?? minBetaLimit;
12910
+ const upperBetaLimit = useProperty(camera, "upperBetaLimit") ?? maxBetaLimit;
12911
+ const lowerRadiusLimit = useProperty(camera, "lowerRadiusLimit");
12912
+ const upperRadiusLimit = useProperty(camera, "upperRadiusLimit");
12484
12913
  // TODO-Iv2: Update defaultValues
12485
- return (jsxs(Fragment, { children: [jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Lower Alpha Limit", target: camera, propertyKey: "lowerAlphaLimit", nullable: true, defaultValue: 0 }), jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Upper Alpha Limit", target: camera, propertyKey: "upperAlphaLimit", nullable: true, defaultValue: Infinity }), jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Lower Beta Limit", target: camera, propertyKey: "lowerBetaLimit", nullable: true, defaultValue: -Math.PI }), jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Upper Beta Limit", target: camera, propertyKey: "upperBetaLimit", nullable: true, defaultValue: Math.PI }), jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Lower Radius Limit", target: camera, propertyKey: "lowerRadiusLimit", nullable: true, defaultValue: 0 }), jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Upper Radius Limit", target: camera, propertyKey: "upperRadiusLimit", nullable: true, defaultValue: 100 }), jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Lower Target Y Limit", target: camera, propertyKey: "lowerTargetYLimit" })] }));
12914
+ return (jsxs(Fragment, { children: [jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Lower Alpha Limit", target: camera, propertyKey: "lowerAlphaLimit", nullable: true, defaultValue: toDisplayAngle(minAlphaLimit), min: toDisplayAngle(minAlphaLimit), max: toDisplayAngle(upperAlphaLimit), unit: useDegrees ? "°" : "rad", convertTo: (value) => (value === null ? value : toDisplayAngle(value, true)), convertFrom: (value) => (value === null ? value : fromDisplayAngle(value)) }), jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Upper Alpha Limit", target: camera, propertyKey: "upperAlphaLimit", nullable: true, defaultValue: toDisplayAngle(maxAlphaLimit), min: toDisplayAngle(lowerAlphaLimit), max: toDisplayAngle(maxAlphaLimit), unit: useDegrees ? "°" : "rad", convertTo: (value) => (value === null ? value : toDisplayAngle(value, true)), convertFrom: (value) => (value === null ? value : fromDisplayAngle(value)) }), jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Lower Beta Limit", target: camera, propertyKey: "lowerBetaLimit", nullable: true, defaultValue: toDisplayAngle(minBetaLimit), min: toDisplayAngle(minBetaLimit), max: toDisplayAngle(upperBetaLimit), unit: useDegrees ? "°" : "rad", convertTo: (value) => (value === null ? value : toDisplayAngle(value, true)), convertFrom: (value) => (value === null ? value : fromDisplayAngle(value)) }), jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Upper Beta Limit", target: camera, propertyKey: "upperBetaLimit", nullable: true, defaultValue: toDisplayAngle(maxBetaLimit), min: toDisplayAngle(lowerBetaLimit), max: toDisplayAngle(maxBetaLimit), unit: useDegrees ? "°" : "rad", convertTo: (value) => (value === null ? value : toDisplayAngle(value, true)), convertFrom: (value) => (value === null ? value : fromDisplayAngle(value)) }), jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Lower Radius Limit", target: camera, propertyKey: "lowerRadiusLimit", nullable: true, defaultValue: 0, min: 0, max: upperRadiusLimit ?? undefined }), jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Upper Radius Limit", target: camera, propertyKey: "upperRadiusLimit", nullable: true, defaultValue: 100, min: lowerRadiusLimit ?? undefined }), jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Lower Target Y Limit", target: camera, propertyKey: "lowerTargetYLimit" })] }));
12486
12915
  };
12487
12916
  const ArcRotateCameraBehaviorsProperties = (props) => {
12488
12917
  const { camera } = props;
@@ -12499,7 +12928,7 @@ const GeospatialCameraTransformProperties = (props) => {
12499
12928
  const pitchMax = limits?.pitchMax ?? Math.PI / 2;
12500
12929
  const radiusMin = limits?.radiusMin ?? 0;
12501
12930
  const radiusMax = limits?.radiusMax ?? Infinity;
12502
- return (jsxs(Fragment, { children: [jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Yaw", description: `Horizontal rotation in ${useDegrees ? "degrees" : "radians"} (0 = north)`, target: camera, propertyKey: "yaw", min: toDisplayAngle(yawMin), max: toDisplayAngle(yawMax), step: toDisplayAngle(0.01), convertTo: (value) => toDisplayAngle(value, true), convertFrom: fromDisplayAngle }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Pitch", description: `Vertical angle in ${useDegrees ? "degrees" : "radians"} (0 = looking down, π/2 = horizon)`, target: camera, propertyKey: "pitch", min: toDisplayAngle(pitchMin), max: toDisplayAngle(pitchMax), step: toDisplayAngle(0.01), convertTo: (value) => toDisplayAngle(value, true), convertFrom: fromDisplayAngle }), radiusMax !== Infinity ? (jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Radius", description: "Distance from the center point.", target: camera, propertyKey: "radius", min: radiusMin, max: radiusMax, step: 0.01 })) : (jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Radius", description: "Distance from the center point.", target: camera, propertyKey: "radius", min: 0, step: 0.01 })), jsx(BoundProperty, { component: Vector3PropertyLine, label: "Center", description: "The point on the globe the camera orbits around.", target: camera, propertyKey: "center" }), jsx(BoundProperty, { component: Vector3PropertyLine, label: "Position", description: "The camera's position.", target: camera, propertyKey: "position" })] }));
12931
+ return (jsxs(Fragment, { children: [jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Yaw", description: `Horizontal rotation in ${useDegrees ? "degrees" : "radians"} (0 = north)`, target: camera, propertyKey: "yaw", min: toDisplayAngle(yawMin), max: toDisplayAngle(yawMax), step: toDisplayAngle(0.01), unit: useDegrees ? "°" : "rad", convertTo: (value) => toDisplayAngle(value, true), convertFrom: fromDisplayAngle }), jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Pitch", description: `Vertical angle in ${useDegrees ? "degrees" : "radians"} (0 = looking down, π/2 = horizon)`, target: camera, propertyKey: "pitch", min: toDisplayAngle(pitchMin), max: toDisplayAngle(pitchMax), step: toDisplayAngle(0.01), unit: useDegrees ? "°" : "rad", convertTo: (value) => toDisplayAngle(value, true), convertFrom: fromDisplayAngle }), radiusMax !== Infinity ? (jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Radius", description: "Distance from the center point.", target: camera, propertyKey: "radius", min: radiusMin, max: radiusMax, step: 0.01 })) : (jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Radius", description: "Distance from the center point.", target: camera, propertyKey: "radius", min: 0, step: 0.01 })), jsx(BoundProperty, { component: Vector3PropertyLine, label: "Center", description: "The point on the globe the camera orbits around.", target: camera, propertyKey: "center" }), jsx(BoundProperty, { component: Vector3PropertyLine, label: "Position", description: "The camera's position.", target: camera, propertyKey: "position" })] }));
12503
12932
  };
12504
12933
  const GeospatialCameraCollisionProperties = (props) => {
12505
12934
  const { camera } = props;
@@ -12576,7 +13005,7 @@ const CameraGeneralProperties = (props) => {
12576
13005
  const { camera } = props;
12577
13006
  const [toDisplayAngle, fromDisplayAngle, useDegrees] = useAngleConverters();
12578
13007
  const mode = useProperty(camera, "mode");
12579
- return (jsxs(Fragment, { children: [jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Near Plane", description: "Anything closer than this will not be drawn.", target: camera, propertyKey: "minZ" }), jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Far Plane", description: "Anything further than this will not be drawn.", target: camera, propertyKey: "maxZ" }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Inertia", target: camera, propertyKey: "inertia", min: 0, max: 1, step: 0.01 }), jsx(BoundProperty, { component: HexPropertyLine, label: "Layer Mask", target: camera, propertyKey: "layerMask" }), jsx(BoundProperty, { component: NumberDropdownPropertyLine, label: "Mode", options: CameraModes, target: camera, propertyKey: "mode" }), jsx(Collapse, { visible: mode === Camera.PERSPECTIVE_CAMERA, children: jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "FOV", description: `Field of view in ${useDegrees ? "degrees" : "radians"}`, target: camera, propertyKey: "fov", min: toDisplayAngle(0.1), max: toDisplayAngle(Math.PI), step: toDisplayAngle(0.01), convertTo: toDisplayAngle, convertFrom: fromDisplayAngle }) }), jsx(Collapse, { visible: mode === Camera.ORTHOGRAPHIC_CAMERA, children: jsxs("div", { children: [jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Left", target: camera, step: 0.1, propertyKey: "orthoLeft", nullable: true, defaultValue: 0 }), jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Right", target: camera, step: 0.1, propertyKey: "orthoRight", nullable: true, defaultValue: 0 }), jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Top", target: camera, step: 0.1, propertyKey: "orthoTop", nullable: true, defaultValue: 0 }), jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Bottom", target: camera, step: 0.1, propertyKey: "orthoBottom", nullable: true, defaultValue: 0 })] }) })] }));
13008
+ return (jsxs(Fragment, { children: [jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Near Plane", description: "Anything closer than this will not be drawn.", target: camera, propertyKey: "minZ" }), jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Far Plane", description: "Anything further than this will not be drawn.", target: camera, propertyKey: "maxZ" }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Inertia", target: camera, propertyKey: "inertia", min: 0, max: 1, step: 0.01 }), jsx(BoundProperty, { component: HexPropertyLine, label: "Layer Mask", target: camera, propertyKey: "layerMask" }), jsx(BoundProperty, { component: NumberDropdownPropertyLine, label: "Mode", options: CameraModes, target: camera, propertyKey: "mode" }), jsx(Collapse, { visible: mode === Camera.PERSPECTIVE_CAMERA, children: jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "FOV", description: `Field of view in ${useDegrees ? "degrees" : "radians"}`, target: camera, propertyKey: "fov", min: toDisplayAngle(0.1), max: toDisplayAngle(Math.PI), step: toDisplayAngle(0.01), unit: useDegrees ? "°" : "rad", convertTo: toDisplayAngle, convertFrom: fromDisplayAngle }) }), jsx(Collapse, { visible: mode === Camera.ORTHOGRAPHIC_CAMERA, children: jsxs("div", { children: [jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Left", target: camera, step: 0.1, propertyKey: "orthoLeft", nullable: true, defaultValue: 0 }), jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Right", target: camera, step: 0.1, propertyKey: "orthoRight", nullable: true, defaultValue: 0 }), jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Top", target: camera, step: 0.1, propertyKey: "orthoTop", nullable: true, defaultValue: 0 }), jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Bottom", target: camera, step: 0.1, propertyKey: "orthoBottom", nullable: true, defaultValue: 0 })] }) })] }));
12580
13009
  };
12581
13010
 
12582
13011
  const FollowCameraTransformProperties = (props) => {
@@ -13012,7 +13441,7 @@ const ShadowsSetupProperties = ({ context: shadowLight }) => {
13012
13441
  };
13013
13442
 
13014
13443
  const SpotLightSetupProperties = ({ context: spotLight }) => {
13015
- return (jsxs(Fragment, { children: [jsx(BoundProperty, { label: "Diffuse", component: Color3PropertyLine, target: spotLight, propertyKey: "diffuse" }), jsx(BoundProperty, { label: "Specular", component: Color3PropertyLine, target: spotLight, propertyKey: "specular" }), jsx(BoundProperty, { label: "Direction", component: Vector3PropertyLine, target: spotLight, propertyKey: "direction" }), jsx(BoundProperty, { label: "Position", component: Vector3PropertyLine, target: spotLight, propertyKey: "position" }), jsx(BoundProperty, { label: "Angle", component: SyncedSliderPropertyLine, target: spotLight, propertyKey: "angle", convertTo: Tools.ToDegrees, convertFrom: Tools.ToRadians, min: 0, max: 90, step: 0.1 }), jsx(BoundProperty, { label: "InnerAngle", component: SyncedSliderPropertyLine, target: spotLight, propertyKey: "innerAngle", convertTo: Tools.ToDegrees, convertFrom: Tools.ToRadians, min: 0, max: 90, step: 0.1 }), jsx(BoundProperty, { label: "Exponent", component: NumberInputPropertyLine, target: spotLight, propertyKey: "exponent" })] }));
13444
+ return (jsxs(Fragment, { children: [jsx(BoundProperty, { label: "Diffuse", component: Color3PropertyLine, target: spotLight, propertyKey: "diffuse" }), jsx(BoundProperty, { label: "Specular", component: Color3PropertyLine, target: spotLight, propertyKey: "specular" }), jsx(BoundProperty, { label: "Direction", component: Vector3PropertyLine, target: spotLight, propertyKey: "direction" }), jsx(BoundProperty, { label: "Position", component: Vector3PropertyLine, target: spotLight, propertyKey: "position" }), jsx(BoundProperty, { label: "Angle", component: SyncedSliderPropertyLine, target: spotLight, propertyKey: "angle", convertTo: Tools.ToDegrees, convertFrom: Tools.ToRadians, min: 0, max: 90, step: 0.1 }), jsx(BoundProperty, { label: "InnerAngle", component: SyncedSliderPropertyLine, target: spotLight, propertyKey: "innerAngle", convertTo: Tools.ToDegrees, convertFrom: Tools.ToRadians, min: 0, max: 90, step: 0.1 }), jsx(BoundProperty, { label: "Intensity", component: NumberInputPropertyLine, target: spotLight, propertyKey: "intensity" }), jsx(BoundProperty, { label: "Exponent", component: NumberInputPropertyLine, target: spotLight, propertyKey: "exponent" })] }));
13016
13445
  };
13017
13446
 
13018
13447
  const LightPropertiesServiceDefinition = {
@@ -13295,7 +13724,7 @@ const Gradient = (props) => {
13295
13724
  // Only use compact mode when there are numeric values (spinbuttons) taking up space
13296
13725
  const hasNumericValues = !(gradient.value1 instanceof Color3 || gradient.value1 instanceof Color4) ||
13297
13726
  (gradient.value2 !== undefined && !(gradient.value2 instanceof Color3 || gradient.value2 instanceof Color4));
13298
- return (jsxs("div", { id: "gradientContainer", className: classes.container, children: [jsx("div", { className: gradient.value1 instanceof Color3 || gradient.value1 instanceof Color4 ? classes.colorWrapper : classes.valueWrapper, children: gradient.value1 instanceof Color3 || gradient.value1 instanceof Color4 ? (jsx(ColorPickerPopup, { value: gradient.value1, onChange: (color) => gradientChange({ ...gradient, value1: color }) })) : (jsx(SyncedSliderInput, { step: 0.01, value: gradient.value1, onChange: (val) => gradientChange({ ...gradient, value1: val }), compact: true })) }), gradient.value2 !== undefined && (jsx("div", { className: gradient.value2 instanceof Color3 || gradient.value2 instanceof Color4 ? classes.colorWrapper : classes.valueWrapper, children: gradient.value2 instanceof Color3 || gradient.value2 instanceof Color4 ? (jsx(ColorPickerPopup, { value: gradient.value2, onChange: (color) => gradientChange({ ...gradient, value2: color }) })) : (jsx(SyncedSliderInput, { step: 0.01, value: gradient.value2, onChange: (val) => gradientChange({ ...gradient, value2: val }), compact: true })) })), jsx("div", { className: classes.stepSliderWrapper, children: jsx(SyncedSliderInput, { notifyOnlyOnRelease: true, min: 0, max: 1, step: 0.01, value: gradient.step, onChange: (val) => gradientChange({ ...gradient, step: val }), compact: hasNumericValues, growSlider: !hasNumericValues }) })] }));
13727
+ return (jsxs("div", { id: "gradientContainer", className: classes.container, children: [jsx("div", { className: gradient.value1 instanceof Color3 || gradient.value1 instanceof Color4 ? classes.colorWrapper : classes.valueWrapper, children: gradient.value1 instanceof Color3 || gradient.value1 instanceof Color4 ? (jsx(ColorPickerPopup, { value: gradient.value1, onChange: (color) => gradientChange({ ...gradient, value1: color }) })) : (jsx(SyncedSliderInput, { step: 0.01, precision: 2, value: gradient.value1, onChange: (val) => gradientChange({ ...gradient, value1: val }), compact: true })) }), gradient.value2 !== undefined && (jsx("div", { className: gradient.value2 instanceof Color3 || gradient.value2 instanceof Color4 ? classes.colorWrapper : classes.valueWrapper, children: gradient.value2 instanceof Color3 || gradient.value2 instanceof Color4 ? (jsx(ColorPickerPopup, { value: gradient.value2, onChange: (color) => gradientChange({ ...gradient, value2: color }) })) : (jsx(SyncedSliderInput, { step: 0.01, precision: 2, value: gradient.value2, onChange: (val) => gradientChange({ ...gradient, value2: val }), compact: true })) })), jsx("div", { className: classes.stepSliderWrapper, children: jsx(SyncedSliderInput, { notifyOnlyOnRelease: true, min: 0, max: 1, step: 0.01, precision: 2, value: gradient.step, onChange: (val) => gradientChange({ ...gradient, step: val }), compact: hasNumericValues, growSlider: !hasNumericValues }) })] }));
13299
13728
  };
13300
13729
  const FactorGradientCast = Gradient;
13301
13730
  const Color3GradientCast = Gradient;
@@ -13771,25 +14200,6 @@ const ComboBox = forwardRef((props, ref) => {
13771
14200
  return (jsxs("div", { className: styles.root, children: [jsx("label", { id: comboId, children: props.label }), jsx(Combobox, { ref: ref, defaultOpen: props.defaultOpen, size: size, root: { className: styles.comboBox }, input: { className: styles.input }, listbox: { className: styles.listbox }, onOptionSelect: onOptionSelect, "aria-labelledby": comboId, placeholder: "Search..", onChange: (ev) => setQuery(ev.target.value), value: query, children: children })] }));
13772
14201
  });
13773
14202
 
13774
- /**
13775
- * A hook that provides a transient state value and a "pulse" function to set it.
13776
- * The transient value is meant to be consumed immediately after being set, and will be cleared on the next render.
13777
- * @typeParam T The type of the transient value.
13778
- * @returns A tuple containing the transient value and a function to "pulse" the state.
13779
- */
13780
- function useImpulse() {
13781
- const impulseRef = useRef(undefined);
13782
- const [, setVersion] = useState(0);
13783
- const pulse = useCallback((value) => {
13784
- impulseRef.current = value;
13785
- setVersion((v) => v + 1);
13786
- }, []);
13787
- // Consume the impulse value and clear it
13788
- const value = impulseRef.current;
13789
- impulseRef.current = undefined;
13790
- return [value, pulse];
13791
- }
13792
-
13793
14203
  const useStyles$j = makeStyles({
13794
14204
  linkDiv: {
13795
14205
  display: "flex",
@@ -14166,7 +14576,7 @@ const PBRBaseMaterialDebugProperties = (props) => {
14166
14576
  const SkyMaterialProperties = (props) => {
14167
14577
  const { material } = props;
14168
14578
  const [toDisplayAngle, fromDisplayAngle, useDegrees] = useAngleConverters();
14169
- return (jsxs(Fragment, { children: [jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Azimuth", description: `Azimuth angle in ${useDegrees ? "degrees" : "radians"}`, target: material, propertyKey: "azimuth", min: toDisplayAngle(0), max: toDisplayAngle(Math.PI * 2), step: toDisplayAngle(0.001), convertTo: toDisplayAngle, convertFrom: fromDisplayAngle, docLink: "https://doc.babylonjs.com/toolsAndResources/assetLibraries/materialsLibrary/skyMat/#configuring-the-sky-material" }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Inclination", description: `Inclination angle in ${useDegrees ? "degrees" : "radians"}`, target: material, propertyKey: "inclination", min: toDisplayAngle(0), max: toDisplayAngle(Math.PI / 2), step: toDisplayAngle(0.001), convertTo: toDisplayAngle, convertFrom: fromDisplayAngle, docLink: "https://doc.babylonjs.com/toolsAndResources/assetLibraries/materialsLibrary/skyMat/#configuring-the-sky-material" }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Turbidity", description: "Atmospheric turbidity.", target: material, propertyKey: "turbidity", min: 0, max: 100, step: 0.1, docLink: "https://doc.babylonjs.com/toolsAndResources/assetLibraries/materialsLibrary/skyMat/#configuring-the-sky-material" }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Luminance", description: "Brightness of the sky (0 to 1).", target: material, propertyKey: "luminance", min: 0, max: 1, step: 0.001, docLink: "https://doc.babylonjs.com/toolsAndResources/assetLibraries/materialsLibrary/skyMat/#configuring-the-sky-material" }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Rayleigh", description: "Rayleigh scattering coefficient (0 to 4).", target: material, propertyKey: "rayleigh", min: 0, max: 4, step: 0.001, docLink: "https://doc.babylonjs.com/toolsAndResources/assetLibraries/materialsLibrary/skyMat/#configuring-the-sky-material" }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Mie Directional G", description: "Mie directional scattering (0 to 1).", target: material, propertyKey: "mieDirectionalG", min: 0, max: 1, step: 0.001, docLink: "https://doc.babylonjs.com/toolsAndResources/assetLibraries/materialsLibrary/skyMat/#configuring-the-sky-material" }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Mie Coefficient", description: "Mie scattering coefficient (0 to 1).", target: material, propertyKey: "mieCoefficient", min: 0, max: 1, step: 0.001, docLink: "https://doc.babylonjs.com/toolsAndResources/assetLibraries/materialsLibrary/skyMat/#configuring-the-sky-material" }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Distance", description: "Distance to the sky dome (0 to 1000 units).", target: material, propertyKey: "distance", min: 0, max: 1000, step: 0.1, docLink: "https://doc.babylonjs.com/toolsAndResources/assetLibraries/materialsLibrary/skyMat/#configuring-the-sky-material" }), jsx(BoundProperty, { component: SwitchPropertyLine, label: "Use Sun Pos", description: "Enable custom sun position.", target: material, propertyKey: "useSunPosition", docLink: "https://doc.babylonjs.com/toolsAndResources/assetLibraries/materialsLibrary/skyMat/#configuring-the-sky-material" }), jsx(BoundProperty, { component: Vector3PropertyLine, label: "Sun Position", description: "Custom sun position (Vector3).", target: material, propertyKey: "sunPosition", docLink: "https://doc.babylonjs.com/toolsAndResources/assetLibraries/materialsLibrary/skyMat/#configuring-the-sky-material" }), jsx(BoundProperty, { component: Vector3PropertyLine, label: "Camera Offset", description: "Offset for the camera (Vector3).", target: material, propertyKey: "cameraOffset", docLink: "https://doc.babylonjs.com/toolsAndResources/assetLibraries/materialsLibrary/skyMat/#keeping-the-horizon-relative-to-the-camera-elevation" })] }));
14579
+ return (jsxs(Fragment, { children: [jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Azimuth", description: `Azimuth angle in ${useDegrees ? "degrees" : "radians"}`, target: material, propertyKey: "azimuth", step: toDisplayAngle(0.001), unit: useDegrees ? "°" : "rad", convertTo: toDisplayAngle, convertFrom: fromDisplayAngle, docLink: "https://doc.babylonjs.com/toolsAndResources/assetLibraries/materialsLibrary/skyMat/#configuring-the-sky-material" }), jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Inclination", description: `Inclination angle in ${useDegrees ? "degrees" : "radians"}`, target: material, propertyKey: "inclination", min: toDisplayAngle(0), max: toDisplayAngle(Math.PI / 2), step: toDisplayAngle(0.001), unit: useDegrees ? "°" : "rad", convertTo: toDisplayAngle, convertFrom: fromDisplayAngle, docLink: "https://doc.babylonjs.com/toolsAndResources/assetLibraries/materialsLibrary/skyMat/#configuring-the-sky-material" }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Turbidity", description: "Atmospheric turbidity.", target: material, propertyKey: "turbidity", min: 0, max: 100, step: 0.1, docLink: "https://doc.babylonjs.com/toolsAndResources/assetLibraries/materialsLibrary/skyMat/#configuring-the-sky-material" }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Luminance", description: "Brightness of the sky (0 to 1).", target: material, propertyKey: "luminance", min: 0, max: 1, step: 0.001, docLink: "https://doc.babylonjs.com/toolsAndResources/assetLibraries/materialsLibrary/skyMat/#configuring-the-sky-material" }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Rayleigh", description: "Rayleigh scattering coefficient (0 to 4).", target: material, propertyKey: "rayleigh", min: 0, max: 4, step: 0.001, docLink: "https://doc.babylonjs.com/toolsAndResources/assetLibraries/materialsLibrary/skyMat/#configuring-the-sky-material" }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Mie Directional G", description: "Mie directional scattering (0 to 1).", target: material, propertyKey: "mieDirectionalG", min: 0, max: 1, step: 0.001, docLink: "https://doc.babylonjs.com/toolsAndResources/assetLibraries/materialsLibrary/skyMat/#configuring-the-sky-material" }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Mie Coefficient", description: "Mie scattering coefficient (0 to 1).", target: material, propertyKey: "mieCoefficient", min: 0, max: 1, step: 0.001, docLink: "https://doc.babylonjs.com/toolsAndResources/assetLibraries/materialsLibrary/skyMat/#configuring-the-sky-material" }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Distance", description: "Distance to the sky dome (0 to 1000 units).", target: material, propertyKey: "distance", min: 0, max: 1000, step: 0.1, docLink: "https://doc.babylonjs.com/toolsAndResources/assetLibraries/materialsLibrary/skyMat/#configuring-the-sky-material" }), jsx(BoundProperty, { component: SwitchPropertyLine, label: "Use Sun Pos", description: "Enable custom sun position.", target: material, propertyKey: "useSunPosition", docLink: "https://doc.babylonjs.com/toolsAndResources/assetLibraries/materialsLibrary/skyMat/#configuring-the-sky-material" }), jsx(BoundProperty, { component: Vector3PropertyLine, label: "Sun Position", description: "Custom sun position (Vector3).", target: material, propertyKey: "sunPosition", docLink: "https://doc.babylonjs.com/toolsAndResources/assetLibraries/materialsLibrary/skyMat/#configuring-the-sky-material" }), jsx(BoundProperty, { component: Vector3PropertyLine, label: "Camera Offset", description: "Offset for the camera (Vector3).", target: material, propertyKey: "cameraOffset", docLink: "https://doc.babylonjs.com/toolsAndResources/assetLibraries/materialsLibrary/skyMat/#keeping-the-horizon-relative-to-the-camera-elevation" })] }));
14170
14580
  };
14171
14581
 
14172
14582
  const StandardMaterialGeneralProperties = (props) => {
@@ -14336,35 +14746,35 @@ const MaterialPropertiesServiceDefinition = {
14336
14746
  predicate: (entity) => entity instanceof OpenPBRMaterial,
14337
14747
  content: [
14338
14748
  {
14339
- section: "Base",
14749
+ section: "OpenPBR",
14340
14750
  component: ({ context }) => jsx(OpenPBRMaterialBaseProperties, { material: context }),
14341
14751
  },
14342
14752
  {
14343
- section: "Specular",
14753
+ section: "OpenPBR",
14344
14754
  component: ({ context }) => jsx(OpenPBRMaterialSpecularProperties, { material: context }),
14345
14755
  },
14346
14756
  {
14347
- section: "Transmission",
14757
+ section: "OpenPBR",
14348
14758
  component: ({ context }) => jsx(OpenPBRMaterialTransmissionProperties, { material: context }),
14349
14759
  },
14350
14760
  {
14351
- section: "Coat",
14761
+ section: "OpenPBR",
14352
14762
  component: ({ context }) => jsx(OpenPBRMaterialCoatProperties, { material: context }),
14353
14763
  },
14354
14764
  {
14355
- section: "Fuzz",
14765
+ section: "OpenPBR",
14356
14766
  component: ({ context }) => jsx(OpenPBRMaterialFuzzProperties, { material: context }),
14357
14767
  },
14358
14768
  {
14359
- section: "Emission",
14769
+ section: "OpenPBR",
14360
14770
  component: ({ context }) => jsx(OpenPBRMaterialEmissionProperties, { material: context }),
14361
14771
  },
14362
14772
  {
14363
- section: "Thin Film",
14773
+ section: "OpenPBR",
14364
14774
  component: ({ context }) => jsx(OpenPBRMaterialThinFilmProperties, { material: context }),
14365
14775
  },
14366
14776
  {
14367
- section: "Geometry",
14777
+ section: "OpenPBR",
14368
14778
  component: ({ context }) => jsx(OpenPBRMaterialGeometryProperties, { material: context }),
14369
14779
  },
14370
14780
  ],
@@ -17336,7 +17746,7 @@ const SpriteGeneralProperties = (props) => {
17336
17746
  const SpriteTransformProperties = (props) => {
17337
17747
  const { sprite } = props;
17338
17748
  const [toDisplayAngle, fromDisplayAngle, useDegrees] = useAngleConverters();
17339
- return (jsxs(Fragment, { children: [jsx(BoundProperty, { component: Vector3PropertyLine, label: "Position", target: sprite, propertyKey: "position" }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Angle", description: `Rotation angle of the sprite in ${useDegrees ? "degrees" : "radians"}`, min: 0, max: toDisplayAngle(Math.PI * 2), step: toDisplayAngle(0.01), target: sprite, propertyKey: "angle", convertTo: toDisplayAngle, convertFrom: fromDisplayAngle }, "Angle"), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Width", description: "Width of the sprite (in world space units)", target: sprite, propertyKey: "width" }, "Width"), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Height", description: "Height of the sprite (in world space units)", target: sprite, propertyKey: "height" }, "Height")] }));
17749
+ return (jsxs(Fragment, { children: [jsx(BoundProperty, { component: Vector3PropertyLine, label: "Position", target: sprite, propertyKey: "position" }), jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Angle", description: `Rotation angle of the sprite in ${useDegrees ? "degrees" : "radians"}`, step: toDisplayAngle(0.01), unit: useDegrees ? "°" : "rad", target: sprite, propertyKey: "angle", convertTo: toDisplayAngle, convertFrom: fromDisplayAngle }, "Angle"), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Width", description: "Width of the sprite (in world space units)", target: sprite, propertyKey: "width" }, "Width"), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Height", description: "Height of the sprite (in world space units)", target: sprite, propertyKey: "height" }, "Height")] }));
17340
17750
  };
17341
17751
  const SpriteAnimationProperties = (props) => {
17342
17752
  const { sprite } = props;
@@ -18930,7 +19340,7 @@ const Contrast = {
18930
19340
  const handleExposureChange = (_, data) => {
18931
19341
  setExposure(data.value);
18932
19342
  };
18933
- return (jsxs("div", { className: classes.settingsContainer, children: [jsxs("div", { className: classes.sliderRow, children: [jsxs(Label, { children: ["Contrast: ", contrast] }), jsx(Slider, { min: -100, max: 100, value: contrast, onChange: handleContrastChange })] }), jsxs("div", { className: classes.sliderRow, children: [jsxs(Label, { children: ["Exposure: ", exposure] }), jsx(Slider, { min: -100, max: 100, value: exposure, onChange: handleExposureChange })] })] }));
19343
+ return (jsxs("div", { className: classes.settingsContainer, children: [jsxs("div", { className: classes.sliderRow, children: [jsxs(Label, { children: ["Contrast: ", contrast] }), jsx(Slider$1, { min: -100, max: 100, value: contrast, onChange: handleContrastChange })] }), jsxs("div", { className: classes.sliderRow, children: [jsxs(Label, { children: ["Exposure: ", exposure] }), jsx(Slider$1, { min: -100, max: 100, value: exposure, onChange: handleExposureChange })] })] }));
18934
19344
  },
18935
19345
  };
18936
19346
  },
@@ -19152,7 +19562,7 @@ const Paintbrush = {
19152
19562
  const handleWidthChange = (_, data) => {
19153
19563
  setWidth(data.value);
19154
19564
  };
19155
- return (jsx("div", { className: classes.settingsContainer, children: jsxs("div", { className: classes.sliderRow, children: [jsxs(Label, { children: ["Size: ", width] }), jsx(Slider, { min: 1, max: 100, value: width, onChange: handleWidthChange })] }) }));
19565
+ return (jsx("div", { className: classes.settingsContainer, children: jsxs("div", { className: classes.sliderRow, children: [jsxs(Label, { children: ["Size: ", width] }), jsx(Slider$1, { min: 1, max: 100, value: width, onChange: handleWidthChange })] }) }));
19156
19566
  },
19157
19567
  };
19158
19568
  },
@@ -19397,7 +19807,7 @@ const TransformProperties = (props) => {
19397
19807
  const quatRotation = useQuaternionProperty(transform, "rotationQuaternion");
19398
19808
  const [useDegrees] = useSetting(UseDegreesSettingDescriptor);
19399
19809
  const [useEuler] = useSetting(UseEulerSettingDescriptor);
19400
- return (jsxs(Fragment, { children: [jsx(BoundProperty, { component: Vector3PropertyLine, label: "Position", target: transform, propertyKey: "position" }), quatRotation ? (jsx(Property, { component: QuaternionPropertyLine, label: "Rotation Quaternion", propertyPath: "rotationQuaternion", value: quatRotation, onChange: (val) => (transform.rotationQuaternion = val), useDegrees: useDegrees, useEuler: useEuler })) : (jsx(BoundProperty, { component: RotationVectorPropertyLine, label: "Rotation", target: transform, propertyKey: "rotation", useDegrees: useDegrees })), jsx(BoundProperty, { component: Vector3PropertyLine, label: "Scaling", target: transform, propertyKey: "scaling" })] }));
19810
+ return (jsxs(Fragment, { children: [jsx(BoundProperty, { component: Vector3PropertyLine, label: "Position", target: transform, propertyKey: "position" }), quatRotation ? (jsx(Property, { component: QuaternionPropertyLine, label: "Rotation", propertyPath: "rotationQuaternion", value: quatRotation, onChange: (val) => (transform.rotationQuaternion = val), useDegrees: useDegrees, useEuler: useEuler })) : (jsx(BoundProperty, { component: RotationVectorPropertyLine, label: "Rotation", target: transform, propertyKey: "rotation", useDegrees: useDegrees })), jsx(BoundProperty, { component: Vector3PropertyLine, label: "Scaling", target: transform, propertyKey: "scaling", step: 0.1 })] }));
19401
19811
  };
19402
19812
 
19403
19813
  const TransformPropertiesServiceDefinition = {
@@ -19425,8 +19835,8 @@ const TransformPropertiesServiceDefinition = {
19425
19835
 
19426
19836
  const AnimationGroupExplorerServiceDefinition = {
19427
19837
  friendlyName: "Animation Group Explorer",
19428
- consumes: [SceneExplorerServiceIdentity, SceneContextIdentity],
19429
- factory: (sceneExplorerService, sceneContext) => {
19838
+ consumes: [SceneExplorerServiceIdentity, SceneContextIdentity, WatcherServiceIdentity],
19839
+ factory: (sceneExplorerService, sceneContext, watcherService) => {
19430
19840
  const scene = sceneContext.currentScene;
19431
19841
  if (!scene) {
19432
19842
  return undefined;
@@ -19439,11 +19849,7 @@ const AnimationGroupExplorerServiceDefinition = {
19439
19849
  getEntityDisplayInfo: (entity) => {
19440
19850
  const namedEntity = entity instanceof AnimationGroup ? entity : entity.animation;
19441
19851
  const onChangeObservable = new Observable();
19442
- const nameHookToken = InterceptProperty(namedEntity, "name", {
19443
- afterSet: () => {
19444
- onChangeObservable.notifyObservers();
19445
- },
19446
- });
19852
+ const nameHookToken = watcherService.watchProperty(namedEntity, "name", () => onChangeObservable.notifyObservers());
19447
19853
  return {
19448
19854
  get name() {
19449
19855
  return namedEntity.name;
@@ -19509,8 +19915,8 @@ const AnimationGroupExplorerServiceDefinition = {
19509
19915
 
19510
19916
  const AtmosphereExplorerServiceDefinition = {
19511
19917
  friendlyName: "Atmosphere Explorer",
19512
- consumes: [SceneExplorerServiceIdentity, SceneContextIdentity],
19513
- factory: (sceneExplorerService, sceneContext) => {
19918
+ consumes: [SceneExplorerServiceIdentity, SceneContextIdentity, WatcherServiceIdentity],
19919
+ factory: (sceneExplorerService, sceneContext, watcherService) => {
19514
19920
  const scene = sceneContext.currentScene;
19515
19921
  if (!scene) {
19516
19922
  return undefined;
@@ -19521,11 +19927,7 @@ const AtmosphereExplorerServiceDefinition = {
19521
19927
  getRootEntities: () => (scene.getExternalData("atmosphere") ? [scene.getExternalData("atmosphere")] : []),
19522
19928
  getEntityDisplayInfo: (atmosphere) => {
19523
19929
  const onChangeObservable = new Observable();
19524
- const nameHookToken = InterceptProperty(atmosphere, "name", {
19525
- afterSet: () => {
19526
- onChangeObservable.notifyObservers();
19527
- },
19528
- });
19930
+ const nameHookToken = watcherService.watchProperty(atmosphere, "name", () => onChangeObservable.notifyObservers());
19529
19931
  return {
19530
19932
  get name() {
19531
19933
  return atmosphere.name;
@@ -19584,8 +19986,8 @@ const DisposableCommandServiceDefinition = {
19584
19986
 
19585
19987
  const EffectLayerExplorerServiceDefinition = {
19586
19988
  friendlyName: "Effect Layer Explorer",
19587
- consumes: [SceneExplorerServiceIdentity, SceneContextIdentity],
19588
- factory: (sceneExplorerService, sceneContext) => {
19989
+ consumes: [SceneExplorerServiceIdentity, SceneContextIdentity, WatcherServiceIdentity],
19990
+ factory: (sceneExplorerService, sceneContext, watcherService) => {
19589
19991
  const scene = sceneContext.currentScene;
19590
19992
  if (!scene) {
19591
19993
  return undefined;
@@ -19596,11 +19998,7 @@ const EffectLayerExplorerServiceDefinition = {
19596
19998
  getRootEntities: () => scene.effectLayers,
19597
19999
  getEntityDisplayInfo: (effectLayer) => {
19598
20000
  const onChangeObservable = new Observable();
19599
- const nameHookToken = InterceptProperty(effectLayer, "name", {
19600
- afterSet: () => {
19601
- onChangeObservable.notifyObservers();
19602
- },
19603
- });
20001
+ const nameHookToken = watcherService.watchProperty(effectLayer, "name", () => onChangeObservable.notifyObservers());
19604
20002
  return {
19605
20003
  get name() {
19606
20004
  return effectLayer.name;
@@ -19626,8 +20024,8 @@ const EffectLayerExplorerServiceDefinition = {
19626
20024
 
19627
20025
  const FrameGraphExplorerServiceDefinition = {
19628
20026
  friendlyName: "Frame Graph Explorer",
19629
- consumes: [SceneExplorerServiceIdentity, SceneContextIdentity],
19630
- factory: (sceneExplorerService, sceneContext) => {
20027
+ consumes: [SceneExplorerServiceIdentity, SceneContextIdentity, WatcherServiceIdentity],
20028
+ factory: (sceneExplorerService, sceneContext, watcherService) => {
19631
20029
  const scene = sceneContext.currentScene;
19632
20030
  if (!scene) {
19633
20031
  return undefined;
@@ -19638,11 +20036,7 @@ const FrameGraphExplorerServiceDefinition = {
19638
20036
  getRootEntities: () => scene.frameGraphs,
19639
20037
  getEntityDisplayInfo: (frameGraph) => {
19640
20038
  const onChangeObservable = new Observable();
19641
- const nameHookToken = InterceptProperty(frameGraph, "name", {
19642
- afterSet: () => {
19643
- onChangeObservable.notifyObservers();
19644
- },
19645
- });
20039
+ const nameHookToken = watcherService.watchProperty(frameGraph, "name", () => onChangeObservable.notifyObservers());
19646
20040
  return {
19647
20041
  get name() {
19648
20042
  return frameGraph.name;
@@ -19663,9 +20057,7 @@ const FrameGraphExplorerServiceDefinition = {
19663
20057
  order: 900 /* DefaultCommandsOrder.FrameGraphPlay */,
19664
20058
  getCommand: (frameGraph) => {
19665
20059
  const onChangeObservable = new Observable();
19666
- const frameGraphHook = InterceptProperty(scene, "frameGraph", {
19667
- afterSet: () => onChangeObservable.notifyObservers(),
19668
- });
20060
+ const frameGraphHook = watcherService.watchProperty(scene, "frameGraph", () => onChangeObservable.notifyObservers());
19669
20061
  return {
19670
20062
  type: "toggle",
19671
20063
  displayName: "Make Active",
@@ -19728,8 +20120,8 @@ function IsControl(entity) {
19728
20120
  }
19729
20121
  const GuiExplorerServiceDefinition = {
19730
20122
  friendlyName: "GUI Explorer",
19731
- consumes: [SceneExplorerServiceIdentity, SceneContextIdentity],
19732
- factory: (sceneExplorerService, sceneContext) => {
20123
+ consumes: [SceneExplorerServiceIdentity, SceneContextIdentity, WatcherServiceIdentity],
20124
+ factory: (sceneExplorerService, sceneContext, watcherService) => {
19733
20125
  const scene = sceneContext.currentScene;
19734
20126
  if (!scene) {
19735
20127
  return undefined;
@@ -19755,10 +20147,8 @@ const GuiExplorerServiceDefinition = {
19755
20147
  const disposeActions = [];
19756
20148
  const onChangeObservable = new Observable();
19757
20149
  disposeActions.push(() => onChangeObservable.clear());
19758
- const nameHookToken = InterceptProperty(entity, "name", {
19759
- afterSet: () => {
19760
- onChangeObservable.notifyObservers();
19761
- },
20150
+ const nameHookToken = watcherService.watchProperty(entity, "name", () => {
20151
+ onChangeObservable.notifyObservers();
19762
20152
  });
19763
20153
  disposeActions.push(() => nameHookToken.dispose());
19764
20154
  if (!IsAdvancedDynamicTexture(entity) && IsContainer(entity)) {
@@ -19815,9 +20205,7 @@ const GuiExplorerServiceDefinition = {
19815
20205
  order: 1000 /* DefaultCommandsOrder.GuiHighlight */,
19816
20206
  getCommand: (control) => {
19817
20207
  const onChangeObservable = new Observable();
19818
- const showBoundingBoxHook = InterceptProperty(control, "isHighlighted", {
19819
- afterSet: () => onChangeObservable.notifyObservers(),
19820
- });
20208
+ const showBoundingBoxHook = watcherService.watchProperty(control, "isHighlighted", () => onChangeObservable.notifyObservers());
19821
20209
  return {
19822
20210
  type: "toggle",
19823
20211
  get displayName() {
@@ -19873,8 +20261,8 @@ const GuiExplorerServiceDefinition = {
19873
20261
 
19874
20262
  const MaterialExplorerServiceDefinition = {
19875
20263
  friendlyName: "Material Explorer",
19876
- consumes: [SceneExplorerServiceIdentity, SceneContextIdentity],
19877
- factory: (sceneExplorerService, sceneContext) => {
20264
+ consumes: [SceneExplorerServiceIdentity, SceneContextIdentity, WatcherServiceIdentity],
20265
+ factory: (sceneExplorerService, sceneContext, watcherService) => {
19878
20266
  const scene = sceneContext.currentScene;
19879
20267
  if (!scene) {
19880
20268
  return undefined;
@@ -19885,11 +20273,7 @@ const MaterialExplorerServiceDefinition = {
19885
20273
  getRootEntities: () => [...scene.materials, ...scene.multiMaterials],
19886
20274
  getEntityDisplayInfo: (material) => {
19887
20275
  const onChangeObservable = new Observable();
19888
- const nameHookToken = InterceptProperty(material, "name", {
19889
- afterSet: () => {
19890
- onChangeObservable.notifyObservers();
19891
- },
19892
- });
20276
+ const nameHookToken = watcherService.watchProperty(material, "name", () => onChangeObservable.notifyObservers());
19893
20277
  return {
19894
20278
  get name() {
19895
20279
  return material.name;
@@ -19929,8 +20313,8 @@ const MaterialExplorerServiceDefinition = {
19929
20313
 
19930
20314
  const NodeExplorerServiceDefinition = {
19931
20315
  friendlyName: "Node Explorer",
19932
- consumes: [SceneExplorerServiceIdentity, SceneContextIdentity, GizmoServiceIdentity],
19933
- factory: (sceneExplorerService, sceneContext, gizmoService) => {
20316
+ consumes: [SceneExplorerServiceIdentity, SceneContextIdentity, GizmoServiceIdentity, WatcherServiceIdentity],
20317
+ factory: (sceneExplorerService, sceneContext, gizmoService, watcherService) => {
19934
20318
  const scene = sceneContext.currentScene;
19935
20319
  if (!scene) {
19936
20320
  return undefined;
@@ -19969,14 +20353,8 @@ const NodeExplorerServiceDefinition = {
19969
20353
  getEntityChildren: (node) => node.getChildren(),
19970
20354
  getEntityDisplayInfo: (node) => {
19971
20355
  const onChangeObservable = new Observable();
19972
- const nameHookToken = InterceptProperty(node, "name", {
19973
- afterSet: () => onChangeObservable.notifyObservers(),
19974
- });
19975
- const parentHookToken = InterceptProperty(node, "parent", {
19976
- afterSet: () => {
19977
- nodeMovedObservable.notifyObservers(node);
19978
- },
19979
- });
20356
+ const nameHookToken = watcherService.watchProperty(node, "name", () => onChangeObservable.notifyObservers());
20357
+ const parentHookToken = watcherService.watchProperty(node, "parent", () => nodeMovedObservable.notifyObservers(node));
19980
20358
  return {
19981
20359
  get name() {
19982
20360
  return node.name;
@@ -20039,9 +20417,7 @@ const NodeExplorerServiceDefinition = {
20039
20417
  order: 1000 /* DefaultCommandsOrder.MeshBoundingBox */,
20040
20418
  getCommand: (mesh) => {
20041
20419
  const onChangeObservable = new Observable();
20042
- const showBoundingBoxHook = InterceptProperty(mesh, "showBoundingBox", {
20043
- afterSet: () => onChangeObservable.notifyObservers(),
20044
- });
20420
+ const showBoundingBoxHook = watcherService.watchProperty(mesh, "showBoundingBox", () => onChangeObservable.notifyObservers());
20045
20421
  return {
20046
20422
  type: "toggle",
20047
20423
  get displayName() {
@@ -20067,9 +20443,7 @@ const NodeExplorerServiceDefinition = {
20067
20443
  order: 1100 /* DefaultCommandsOrder.MeshVisibility */,
20068
20444
  getCommand: (mesh) => {
20069
20445
  const onChangeObservable = new Observable();
20070
- const isVisibleHook = InterceptProperty(mesh, "isVisible", {
20071
- afterSet: () => onChangeObservable.notifyObservers(),
20072
- });
20446
+ const isVisibleHook = watcherService.watchProperty(mesh, "isVisible", () => onChangeObservable.notifyObservers());
20073
20447
  return {
20074
20448
  type: "toggle",
20075
20449
  get displayName() {
@@ -20094,6 +20468,7 @@ const NodeExplorerServiceDefinition = {
20094
20468
  };
20095
20469
  },
20096
20470
  });
20471
+ const getActiveCamera = () => (scene.frameGraph ? FindMainCamera(scene.frameGraph) : scene.activeCamera);
20097
20472
  const activeCameraCommandRegistration = sceneExplorerService.addEntityCommand({
20098
20473
  predicate: (entity) => entity instanceof Camera,
20099
20474
  order: 700 /* DefaultCommandsOrder.CameraActive */,
@@ -20106,15 +20481,28 @@ const NodeExplorerServiceDefinition = {
20106
20481
  return {
20107
20482
  type: "toggle",
20108
20483
  displayName: "Activate and Attach Controls",
20109
- icon: () => (scene.activeCamera === camera ? jsx(VideoFilled, {}) : jsx(VideoRegular, {})),
20484
+ icon: () => {
20485
+ return getActiveCamera() === camera ? jsx(VideoFilled, {}) : jsx(VideoRegular, {});
20486
+ },
20110
20487
  get isEnabled() {
20111
- return scene.activeCamera === camera;
20488
+ return getActiveCamera() === camera;
20112
20489
  },
20113
20490
  set isEnabled(enabled) {
20114
- if (enabled && scene.activeCamera !== camera) {
20115
- scene.activeCamera?.detachControl();
20116
- scene.activeCamera = camera;
20117
- camera.attachControl(true);
20491
+ const activeCamera = getActiveCamera();
20492
+ if (enabled && activeCamera !== camera) {
20493
+ activeCamera?.detachControl();
20494
+ if (scene.frameGraph) {
20495
+ const objectRenderer = FindMainObjectRenderer(scene.frameGraph);
20496
+ if (objectRenderer) {
20497
+ objectRenderer.camera = camera;
20498
+ onChangeObservable.notifyObservers(); // manual trigger, because scene.onActiveCameraChanged won't be triggered by the line above
20499
+ camera.attachControl(true);
20500
+ }
20501
+ }
20502
+ else {
20503
+ scene.activeCamera = camera;
20504
+ camera.attachControl(true);
20505
+ }
20118
20506
  }
20119
20507
  },
20120
20508
  onChange: onChangeObservable,
@@ -20221,8 +20609,8 @@ const NodeExplorerServiceDefinition = {
20221
20609
 
20222
20610
  const ParticleSystemExplorerServiceDefinition = {
20223
20611
  friendlyName: "Particle System Explorer",
20224
- consumes: [SceneExplorerServiceIdentity, SceneContextIdentity],
20225
- factory: (sceneExplorerService, sceneContext) => {
20612
+ consumes: [SceneExplorerServiceIdentity, SceneContextIdentity, WatcherServiceIdentity],
20613
+ factory: (sceneExplorerService, sceneContext, watcherService) => {
20226
20614
  const scene = sceneContext.currentScene;
20227
20615
  if (!scene) {
20228
20616
  return undefined;
@@ -20233,11 +20621,7 @@ const ParticleSystemExplorerServiceDefinition = {
20233
20621
  getRootEntities: () => scene.particleSystems,
20234
20622
  getEntityDisplayInfo: (particleSystem) => {
20235
20623
  const onChangeObservable = new Observable();
20236
- const nameHookToken = InterceptProperty(particleSystem, "name", {
20237
- afterSet: () => {
20238
- onChangeObservable.notifyObservers();
20239
- },
20240
- });
20624
+ const nameHookToken = watcherService.watchProperty(particleSystem, "name", () => onChangeObservable.notifyObservers());
20241
20625
  return {
20242
20626
  get name() {
20243
20627
  return particleSystem.name;
@@ -20277,8 +20661,8 @@ const ParticleSystemExplorerServiceDefinition = {
20277
20661
 
20278
20662
  const PostProcessExplorerServiceDefinition = {
20279
20663
  friendlyName: "Post Process Explorer",
20280
- consumes: [SceneExplorerServiceIdentity, SceneContextIdentity],
20281
- factory: (sceneExplorerService, sceneContext) => {
20664
+ consumes: [SceneExplorerServiceIdentity, SceneContextIdentity, WatcherServiceIdentity],
20665
+ factory: (sceneExplorerService, sceneContext, watcherService) => {
20282
20666
  const scene = sceneContext.currentScene;
20283
20667
  if (!scene) {
20284
20668
  return undefined;
@@ -20289,11 +20673,7 @@ const PostProcessExplorerServiceDefinition = {
20289
20673
  getRootEntities: () => scene.postProcesses,
20290
20674
  getEntityDisplayInfo: (postProcess) => {
20291
20675
  const onChangeObservable = new Observable();
20292
- const nameHookToken = InterceptProperty(postProcess, "name", {
20293
- afterSet: () => {
20294
- onChangeObservable.notifyObservers();
20295
- },
20296
- });
20676
+ const nameHookToken = watcherService.watchProperty(postProcess, "name", () => onChangeObservable.notifyObservers());
20297
20677
  return {
20298
20678
  get name() {
20299
20679
  return postProcess.name;
@@ -20351,8 +20731,8 @@ const RenderingPipelineExplorerServiceDefinition = {
20351
20731
 
20352
20732
  const SkeletonExplorerServiceDefinition = {
20353
20733
  friendlyName: "Skeleton Explorer",
20354
- consumes: [SceneExplorerServiceIdentity, SceneContextIdentity],
20355
- factory: (sceneExplorerService, sceneContext) => {
20734
+ consumes: [SceneExplorerServiceIdentity, SceneContextIdentity, WatcherServiceIdentity],
20735
+ factory: (sceneExplorerService, sceneContext, watcherService) => {
20356
20736
  const scene = sceneContext.currentScene;
20357
20737
  if (!scene) {
20358
20738
  return undefined;
@@ -20365,16 +20745,8 @@ const SkeletonExplorerServiceDefinition = {
20365
20745
  getEntityChildren: (skeletonOrBone) => skeletonOrBone.getChildren(),
20366
20746
  getEntityDisplayInfo: (skeletonOrBone) => {
20367
20747
  const onChangeObservable = new Observable();
20368
- const nameHookToken = InterceptProperty(skeletonOrBone, "name", {
20369
- afterSet: () => onChangeObservable.notifyObservers(),
20370
- });
20371
- const parentHookToken = skeletonOrBone instanceof Skeleton
20372
- ? null
20373
- : InterceptProperty(skeletonOrBone, "parent", {
20374
- afterSet: () => {
20375
- boneMovedObservable.notifyObservers(skeletonOrBone);
20376
- },
20377
- });
20748
+ const nameHookToken = watcherService.watchProperty(skeletonOrBone, "name", () => onChangeObservable.notifyObservers());
20749
+ const parentHookToken = skeletonOrBone instanceof Skeleton ? null : watcherService.watchProperty(skeletonOrBone, "parent", () => boneMovedObservable.notifyObservers(skeletonOrBone));
20378
20750
  return {
20379
20751
  get name() {
20380
20752
  return skeletonOrBone.name;
@@ -20402,8 +20774,8 @@ const SkeletonExplorerServiceDefinition = {
20402
20774
 
20403
20775
  const SoundExplorerServiceDefinition = {
20404
20776
  friendlyName: "Sound Explorer",
20405
- consumes: [SceneExplorerServiceIdentity, SceneContextIdentity],
20406
- factory: (sceneExplorerService, sceneContext) => {
20777
+ consumes: [SceneExplorerServiceIdentity, SceneContextIdentity, WatcherServiceIdentity],
20778
+ factory: (sceneExplorerService, sceneContext, watcherService) => {
20407
20779
  const scene = sceneContext.currentScene;
20408
20780
  if (!scene) {
20409
20781
  return undefined;
@@ -20429,25 +20801,14 @@ const SoundExplorerServiceDefinition = {
20429
20801
  // If _mainSoundTrack is already defined, set up hooks immediately.
20430
20802
  hookMainSoundTrack(scene.mainSoundTrack);
20431
20803
  // Watch for _mainSoundTrack being set (it is lazily created by the mainSoundTrack getter in audioSceneComponent.ts).
20432
- const mainSoundTrackHook = InterceptProperty(scene, "_mainSoundTrack", {
20433
- afterSet: () => hookMainSoundTrack(scene._mainSoundTrack),
20434
- });
20804
+ const mainSoundTrackHook = watcherService.watchProperty(scene, "_mainSoundTrack", () => hookMainSoundTrack(scene._mainSoundTrack));
20435
20805
  const sectionRegistration = sceneExplorerService.addSection({
20436
20806
  displayName: "Sounds",
20437
20807
  order: 1400 /* DefaultSectionsOrder.Sounds */,
20438
20808
  getRootEntities: () => scene.mainSoundTrack?.soundCollection ?? [],
20439
20809
  getEntityDisplayInfo: (sound) => {
20440
20810
  const onChangeObservable = new Observable();
20441
- const displayNameHookToken = InterceptProperty(sound, "name", {
20442
- afterSet: () => {
20443
- onChangeObservable.notifyObservers();
20444
- },
20445
- });
20446
- const nameHookToken = InterceptProperty(sound, "name", {
20447
- afterSet: () => {
20448
- onChangeObservable.notifyObservers();
20449
- },
20450
- });
20811
+ const nameHookToken = watcherService.watchProperty(sound, "name", () => onChangeObservable.notifyObservers());
20451
20812
  return {
20452
20813
  get name() {
20453
20814
  return sound.name;
@@ -20455,7 +20816,6 @@ const SoundExplorerServiceDefinition = {
20455
20816
  onChange: onChangeObservable,
20456
20817
  dispose: () => {
20457
20818
  nameHookToken.dispose();
20458
- displayNameHookToken.dispose();
20459
20819
  onChangeObservable.clear();
20460
20820
  },
20461
20821
  };
@@ -20479,8 +20839,8 @@ const SoundExplorerServiceDefinition = {
20479
20839
 
20480
20840
  const SpriteManagerExplorerServiceDefinition = {
20481
20841
  friendlyName: "Sprite Manager Explorer",
20482
- consumes: [SceneExplorerServiceIdentity, SceneContextIdentity],
20483
- factory: (sceneExplorerService, sceneContext) => {
20842
+ consumes: [SceneExplorerServiceIdentity, SceneContextIdentity, WatcherServiceIdentity],
20843
+ factory: (sceneExplorerService, sceneContext, watcherService) => {
20484
20844
  const scene = sceneContext.currentScene;
20485
20845
  if (!scene) {
20486
20846
  return undefined;
@@ -20492,9 +20852,7 @@ const SpriteManagerExplorerServiceDefinition = {
20492
20852
  getEntityChildren: (spriteEntity) => (spriteEntity instanceof Sprite ? [] : spriteEntity.sprites),
20493
20853
  getEntityDisplayInfo: (spriteEntity) => {
20494
20854
  const onChangeObservable = new Observable();
20495
- const nameHookToken = InterceptProperty(spriteEntity, "name", {
20496
- afterSet: () => onChangeObservable.notifyObservers(),
20497
- });
20855
+ const nameHookToken = watcherService.watchProperty(spriteEntity, "name", () => onChangeObservable.notifyObservers());
20498
20856
  return {
20499
20857
  get name() {
20500
20858
  return spriteEntity.name;
@@ -20552,8 +20910,8 @@ const SpriteManagerExplorerServiceDefinition = {
20552
20910
 
20553
20911
  const TextureExplorerServiceDefinition = {
20554
20912
  friendlyName: "Texture Explorer",
20555
- consumes: [SceneExplorerServiceIdentity, SceneContextIdentity],
20556
- factory: (sceneExplorerService, sceneContext) => {
20913
+ consumes: [SceneExplorerServiceIdentity, SceneContextIdentity, WatcherServiceIdentity],
20914
+ factory: (sceneExplorerService, sceneContext, watcherService) => {
20557
20915
  const scene = sceneContext.currentScene;
20558
20916
  if (!scene) {
20559
20917
  return undefined;
@@ -20564,16 +20922,8 @@ const TextureExplorerServiceDefinition = {
20564
20922
  getRootEntities: () => scene.textures.filter((texture) => texture.getClassName() !== "AdvancedDynamicTexture"),
20565
20923
  getEntityDisplayInfo: (texture) => {
20566
20924
  const onChangeObservable = new Observable();
20567
- const displayNameHookToken = InterceptProperty(texture, "displayName", {
20568
- afterSet: () => {
20569
- onChangeObservable.notifyObservers();
20570
- },
20571
- });
20572
- const nameHookToken = InterceptProperty(texture, "name", {
20573
- afterSet: () => {
20574
- onChangeObservable.notifyObservers();
20575
- },
20576
- });
20925
+ const displayNameHookToken = watcherService.watchProperty(texture, "displayName", () => onChangeObservable.notifyObservers());
20926
+ const nameHookToken = watcherService.watchProperty(texture, "name", () => onChangeObservable.notifyObservers());
20577
20927
  return {
20578
20928
  get name() {
20579
20929
  return texture.displayName || texture.name || `${texture.getClassName() || "Unnamed Texture"} (${texture.uniqueId})`;
@@ -21123,97 +21473,6 @@ const GLTFValidationServiceDefinition = {
21123
21473
  },
21124
21474
  };
21125
21475
 
21126
- const HighlightSelectedEntitySettingDescriptor = {
21127
- key: "HighlightSelectedEntity",
21128
- defaultValue: true,
21129
- };
21130
- const HighlightServiceDefinition = {
21131
- friendlyName: "Highlight Service",
21132
- consumes: [SelectionServiceIdentity, SceneContextIdentity, SettingsStoreIdentity, ThemeServiceIdentity, GizmoServiceIdentity],
21133
- factory: (selectionService, sceneContext, settingsStore, themeService, gizmoService) => {
21134
- let outlineLayer = null;
21135
- let utilityLayer = null;
21136
- let currentScene = null;
21137
- let activeCameraObserver = null;
21138
- function disposeOutlineLayer() {
21139
- outlineLayer?.dispose();
21140
- outlineLayer = null;
21141
- utilityLayer?.dispose();
21142
- utilityLayer = null;
21143
- currentScene = null;
21144
- }
21145
- function getOrCreateOutlineLayer(scene) {
21146
- if (!outlineLayer || currentScene !== scene) {
21147
- disposeOutlineLayer();
21148
- utilityLayer = gizmoService.getUtilityLayer(scene);
21149
- outlineLayer = new SelectionOutlineLayer("InspectorSelectionOutline", utilityLayer.value.utilityLayerScene);
21150
- updateColor(outlineLayer);
21151
- currentScene = scene;
21152
- }
21153
- return outlineLayer;
21154
- }
21155
- function updateColor(outlineLayer) {
21156
- outlineLayer.outlineColor = Color3.FromHexString(themeService.theme.colorBrandForeground1);
21157
- }
21158
- function updateHighlight() {
21159
- const scene = sceneContext.currentScene;
21160
- const entity = selectionService.selectedEntity instanceof AbstractMesh && !(selectionService.selectedEntity instanceof GaussianSplattingMesh)
21161
- ? selectionService.selectedEntity
21162
- : null;
21163
- if (!entity || !settingsStore.readSetting(HighlightSelectedEntitySettingDescriptor) || !scene || !scene.activeCamera) {
21164
- disposeOutlineLayer();
21165
- return;
21166
- }
21167
- const layer = getOrCreateOutlineLayer(scene);
21168
- layer.clearSelection();
21169
- layer.addSelection(entity);
21170
- }
21171
- function watchActiveCamera(scene) {
21172
- activeCameraObserver?.remove();
21173
- activeCameraObserver = null;
21174
- if (scene) {
21175
- activeCameraObserver = scene.onActiveCameraChanged.add(updateHighlight);
21176
- }
21177
- }
21178
- // React to theme changes.
21179
- const themeObserver = themeService.onChanged.add(() => {
21180
- if (outlineLayer) {
21181
- updateColor(outlineLayer);
21182
- }
21183
- });
21184
- // React to selection changes.
21185
- const selectionObserver = selectionService.onSelectedEntityChanged.add(updateHighlight);
21186
- // React to scene changes.
21187
- const sceneObserver = sceneContext.currentSceneObservable.add(() => {
21188
- // Dispose the old layer when the scene changes.
21189
- disposeOutlineLayer();
21190
- watchActiveCamera(sceneContext.currentScene);
21191
- updateHighlight();
21192
- });
21193
- // React to setting changes.
21194
- const settingObserver = settingsStore.onChanged.add((setting) => {
21195
- if (setting === HighlightSelectedEntitySettingDescriptor.key) {
21196
- updateHighlight();
21197
- }
21198
- });
21199
- // Watch active camera on the initial scene.
21200
- watchActiveCamera(sceneContext.currentScene);
21201
- // Initial update.
21202
- updateHighlight();
21203
- return {
21204
- dispose: () => {
21205
- themeObserver.remove();
21206
- selectionObserver.remove();
21207
- sceneObserver.remove();
21208
- settingObserver.remove();
21209
- activeCameraObserver?.remove();
21210
- activeCameraObserver = null;
21211
- disposeOutlineLayer();
21212
- },
21213
- };
21214
- },
21215
- };
21216
-
21217
21476
  const PickingToolbar = (props) => {
21218
21477
  const { scene, selectEntity, gizmoService, ignoreBackfaces, highlightSelectedEntity, onHighlightSelectedEntityChange } = props;
21219
21478
  const meshDataCache = useMemo(() => new WeakMap(), [scene]);
@@ -21322,7 +21581,7 @@ const PickingServiceDefinition = {
21322
21581
  key: "Picking Service",
21323
21582
  verticalLocation: "top",
21324
21583
  horizontalLocation: "left",
21325
- suppressTeachingMoment: true,
21584
+ teachingMoment: false,
21326
21585
  component: () => {
21327
21586
  const scene = useObservableState(() => sceneContext.currentScene, sceneContext.currentSceneObservable);
21328
21587
  const selectEntity = useCallback((entity) => (selectionService.selectedEntity = entity), []);
@@ -21378,7 +21637,11 @@ const UserFeedbackServiceDefinition = {
21378
21637
  key: "User Feedback",
21379
21638
  verticalLocation: "bottom",
21380
21639
  horizontalLocation: "right",
21381
- suppressTeachingMoment: true,
21640
+ order: 100 /* DefaultToolbarItemOrder.Feedback */,
21641
+ teachingMoment: {
21642
+ title: "Feedback",
21643
+ description: "Press this button to give feedback on Inspector v2 and help us prioritize new features and improvements!",
21644
+ },
21382
21645
  component: () => {
21383
21646
  return (jsx(Tooltip, { content: "Give Feedback on Inspector v2", children: jsx(Button, { appearance: "subtle", icon: PersonFeedbackRegular, onClick: () => window.open("https://forum.babylonjs.com/t/introducing-inspector-v2/60937", "_blank") }) }));
21384
21647
  },
@@ -21569,7 +21832,9 @@ function ShowInspector(scene, options = {}) {
21569
21832
  // Tools pane tab and related services.
21570
21833
  ToolsServiceDefinition, ExportServiceDefinition, GLTFAnimationImportServiceDefinition, GLTFLoaderOptionsServiceDefinition, GLTFValidationServiceDefinition, CaptureToolsDefinition,
21571
21834
  // Settings pane tab and related services.
21572
- SettingsServiceDefinition, ShellSettingsServiceDefinition,
21835
+ SettingsServiceDefinition, WatcherSettingsServiceDefinition, ShellSettingsServiceDefinition,
21836
+ // Adds a button to refresh all properties manually (when watcher is in "manual" mode).
21837
+ WatcherRefreshToolbarServiceDefinition,
21573
21838
  // Tracks entity selection state (e.g. which Mesh or Material or other entity is currently selected in scene explorer and bound to the properties pane, etc.).
21574
21839
  SelectionServiceDefinition,
21575
21840
  // Gizmos for manipulating objects in the scene.
@@ -21803,19 +22068,15 @@ function ConvertOptions(v1Options) {
21803
22068
  const { additionalNodes } = v1Options;
21804
22069
  const additionalNodesServiceDefinition = {
21805
22070
  friendlyName: "Additional Nodes (Backward Compatibility)",
21806
- consumes: [SceneExplorerServiceIdentity],
21807
- factory: (sceneExplorerService) => {
22071
+ consumes: [SceneExplorerServiceIdentity, WatcherServiceIdentity],
22072
+ factory: (sceneExplorerService, watcherService) => {
21808
22073
  const sceneExplorerSectionRegistrations = additionalNodes.map((node) => sceneExplorerService.addSection({
21809
22074
  displayName: node.name,
21810
22075
  order: Number.MAX_SAFE_INTEGER,
21811
22076
  getRootEntities: () => node.getContent(),
21812
22077
  getEntityDisplayInfo: (entity) => {
21813
22078
  const onChangeObservable = new Observable();
21814
- const nameHookToken = InterceptProperty(entity, "name", {
21815
- afterSet: () => {
21816
- onChangeObservable.notifyObservers();
21817
- },
21818
- });
22079
+ const nameHookToken = watcherService.watchProperty(entity, "name", () => onChangeObservable.notifyObservers());
21819
22080
  return {
21820
22081
  get name() {
21821
22082
  return entity.name;
@@ -21949,7 +22210,7 @@ class Inspector {
21949
22210
  this.MarkMultipleLineContainerTitlesForHighlighting([title]);
21950
22211
  }
21951
22212
  static MarkMultipleLineContainerTitlesForHighlighting(titles) {
21952
- this._SectionHighlighter?.(titles);
22213
+ this._OnMarkLineContainerObservable.notifyObservers(titles);
21953
22214
  }
21954
22215
  static PopupEmbed() {
21955
22216
  this._PopupToggler?.("right");
@@ -21973,6 +22234,13 @@ class Inspector {
21973
22234
  if (!scene || scene.isDisposed) {
21974
22235
  return;
21975
22236
  }
22237
+ // Inspector setup is async, so we need to cache pending selection requests.
22238
+ // Additionally, we manually track this (rather than relying on the observable's notifyIfTriggered property)
22239
+ // for behavior backward compatibility.
22240
+ let pendingSelection = null;
22241
+ const pendingSelectionObserver = this.OnSelectionChangeObservable.add((entity) => {
22242
+ pendingSelection = entity;
22243
+ });
21976
22244
  let options = ConvertOptions(userOptions);
21977
22245
  const serviceDefinitions = [];
21978
22246
  const popupServiceDefinition = {
@@ -22006,6 +22274,13 @@ class Inspector {
22006
22274
  const legacyObserver = this.OnSelectionChangeObservable.add((entity) => {
22007
22275
  selectionService.selectedEntity = entity;
22008
22276
  });
22277
+ // If a selection was requested before async setup completed, apply it now.
22278
+ if (pendingSelection) {
22279
+ selectionService.selectedEntity = pendingSelection;
22280
+ pendingSelection = null;
22281
+ }
22282
+ // Now the service is alive so we don't need to track pending selection requests.
22283
+ pendingSelectionObserver.remove();
22009
22284
  return {
22010
22285
  dispose: () => {
22011
22286
  selectionServiceObserver.remove();
@@ -22039,12 +22314,17 @@ class Inspector {
22039
22314
  friendlyName: "Section Highlighter Service (Backward Compatibility)",
22040
22315
  consumes: [PropertiesServiceIdentity],
22041
22316
  factory: (propertiesService) => {
22042
- this._SectionHighlighter = (sectionIds) => {
22043
- propertiesService.highlightSections(sectionIds.map((id) => LegacyPropertiesSectionMapping[id] ?? id));
22044
- };
22317
+ const markLineContainerObserver = this._OnMarkLineContainerObservable.add((sections) => {
22318
+ propertiesService.highlightSections(sections.map((id) => LegacyPropertiesSectionMapping[id] ?? id));
22319
+ });
22320
+ // Now the service is alive so we don't need to track pending highlight requests.
22321
+ this._OnMarkLineContainerObservable.notifyIfTriggered = false;
22322
+ this._OnMarkLineContainerObservable.cleanLastNotifiedState();
22045
22323
  return {
22046
22324
  dispose: () => {
22047
- this._SectionHighlighter = null;
22325
+ // Service is being torn down, so start caching pending highlight requests again.
22326
+ markLineContainerObserver.remove();
22327
+ this._OnMarkLineContainerObservable.notifyIfTriggered = true;
22048
22328
  },
22049
22329
  };
22050
22330
  },
@@ -22094,10 +22374,10 @@ class Inspector {
22094
22374
  }
22095
22375
  Inspector._CurrentInstance = null;
22096
22376
  Inspector._PopupToggler = null;
22097
- Inspector._SectionHighlighter = null;
22098
22377
  Inspector._SidePaneOpenCounter = null;
22099
22378
  Inspector.OnSelectionChangeObservable = new Observable();
22100
22379
  Inspector.OnPropertyChangedObservable = new Observable();
22380
+ Inspector._OnMarkLineContainerObservable = new Observable(undefined, true);
22101
22381
 
22102
22382
  var inspector = /*#__PURE__*/Object.freeze({
22103
22383
  __proto__: null,
@@ -22406,5 +22686,5 @@ const TextAreaPropertyLine = (props) => {
22406
22686
  // Attach Inspector v2 to Scene.debugLayer as a side effect for back compat.
22407
22687
  AttachDebugLayer();
22408
22688
 
22409
- export { useEventfulState as $, Accordion as A, Button as B, CheckboxPropertyLine as C, DebugServiceIdentity as D, Property as E, LinkToEntityPropertyLine as F, GizmoServiceIdentity as G, ErrorBoundary as H, Inspector as I, ExtensibleAccordion as J, Theme as K, LinkToEntity as L, MessageBar as M, NumberInputPropertyLine as N, PropertyContext as O, Popover as P, usePropertyChangedNotifier as Q, BuiltInsExtensionFeed as R, SpinButtonPropertyLine as S, TextInputPropertyLine as T, useVector3Property as U, Vector3PropertyLine as V, useColor3Property as W, useColor4Property as X, useQuaternionProperty as Y, MakePropertyHook as Z, useInterceptObservable as _, useProperty as a, Pane as a$, useObservableCollection as a0, useOrderedObservableCollection as a1, usePollingObservable as a2, useResource as a3, useAsyncResource as a4, useSetting as a5, useAngleConverters as a6, MakeTeachingMoment as a7, MakeDialogTeachingMoment as a8, useThemeMode as a9, Color3GradientComponent as aA, Color4GradientComponent as aB, ColorStepGradientComponent as aC, InfoLabel as aD, MakeLazyComponent as aE, List as aF, MaterialSelector as aG, NodeSelector as aH, PositionedPopover as aI, SearchBar as aJ, SearchBox as aK, SkeletonSelector as aL, SpinButton as aM, Switch as aN, SyncedSliderInput as aO, Textarea as aP, TextInput as aQ, TextureSelector as aR, ToastProvider as aS, ToggleButton as aT, Tooltip as aU, UploadButton as aV, ChildWindow as aW, FileUploadLine as aX, FactorGradientList as aY, Color3GradientList as aZ, Color4GradientList as a_, useTheme as aa, InterceptFunction as ab, GetPropertyDescriptor as ac, IsPropertyReadonly as ad, InterceptProperty as ae, ObservableCollection as af, ConstructorFactory as ag, SelectionServiceDefinition as ah, SettingsStore as ai, ShowInspector as aj, useKeyListener as ak, useKeyState as al, useEventListener as am, AccordionSectionItem as an, Checkbox as ao, Collapse as ap, ColorPickerPopup as aq, InputHexField as ar, InputHsvField as as, ComboBox as at, DraggableLine as au, Dropdown as av, NumberDropdown as aw, StringDropdown as ax, EntitySelector as ay, FactorGradientComponent as az, ShellServiceIdentity as b, TextureUpload as b0, BooleanBadgePropertyLine as b1, Color3PropertyLine as b2, Color4PropertyLine as b3, ComboBoxPropertyLine as b4, HexPropertyLine as b5, LinkPropertyLine as b6, PropertyLine as b7, LineContainer as b8, PlaceholderPropertyLine as b9, StringifiedPropertyLine as ba, SwitchPropertyLine as bb, SyncedSliderPropertyLine as bc, TextAreaPropertyLine as bd, TextPropertyLine as be, RotationVectorPropertyLine as bf, QuaternionPropertyLine as bg, Vector2PropertyLine as bh, Vector4PropertyLine as bi, SceneContextIdentity as c, SelectionServiceIdentity as d, useObservableState as e, AccordionSection as f, ButtonLine as g, ToolsServiceIdentity as h, useExtensionManager as i, MakePopoverTeachingMoment as j, TeachingMoment as k, Link as l, SidePaneContainer as m, PropertiesServiceIdentity as n, SceneExplorerServiceIdentity as o, SettingsServiceIdentity as p, StatsServiceIdentity as q, ThemeServiceIdentity as r, SettingsStoreIdentity as s, ConvertOptions as t, useToast as u, AttachDebugLayer as v, DetachDebugLayer as w, NumberDropdownPropertyLine as x, StringDropdownPropertyLine as y, BoundProperty as z };
22410
- //# sourceMappingURL=index-Cmd13UXt.js.map
22689
+ export { useEventfulState as $, Accordion as A, Button as B, CheckboxPropertyLine as C, DebugServiceIdentity as D, ErrorBoundary as E, ExtensibleAccordion as F, GizmoServiceIdentity as G, Theme as H, Inspector as I, TeachingMoment as J, PropertyContext as K, LinkToEntity as L, MessageBar as M, NumberInputPropertyLine as N, usePropertyChangedNotifier as O, Popover as P, BuiltInsExtensionFeed as Q, useVector3Property as R, SpinButtonPropertyLine as S, TextInputPropertyLine as T, useColor3Property as U, Vector3PropertyLine as V, WatcherServiceIdentity as W, useColor4Property as X, useQuaternionProperty as Y, MakePropertyHook as Z, useInterceptObservable as _, useProperty as a, Color3GradientList as a$, useObservableCollection as a0, useOrderedObservableCollection as a1, usePollingObservable as a2, useResource as a3, useAsyncResource as a4, useSetting as a5, useAngleConverters as a6, MakeTeachingMoment as a7, MakeDialogTeachingMoment as a8, MakePopoverTeachingMoment as a9, FactorGradientComponent as aA, Color3GradientComponent as aB, Color4GradientComponent as aC, ColorStepGradientComponent as aD, InfoLabel as aE, MakeLazyComponent as aF, List as aG, MaterialSelector as aH, NodeSelector as aI, PositionedPopover as aJ, SearchBar as aK, SearchBox as aL, SkeletonSelector as aM, Slider as aN, SpinButton as aO, Switch as aP, SyncedSliderInput as aQ, Textarea as aR, TextInput as aS, TextureSelector as aT, ToastProvider as aU, ToggleButton as aV, Tooltip as aW, UploadButton as aX, ChildWindow as aY, FileUploadLine as aZ, FactorGradientList as a_, useThemeMode as aa, useTheme as ab, InterceptFunction as ac, GetPropertyDescriptor as ad, IsPropertyReadonly as ae, InterceptProperty as af, ObservableCollection as ag, ConstructorFactory as ah, SelectionServiceDefinition as ai, SettingsStore as aj, ShowInspector as ak, useKeyListener as al, useKeyState as am, useEventListener as an, AccordionSectionItem as ao, Checkbox as ap, Collapse as aq, ColorPickerPopup as ar, InputHexField as as, InputHsvField as at, ComboBox as au, DraggableLine as av, Dropdown as aw, NumberDropdown as ax, StringDropdown as ay, EntitySelector as az, ShellServiceIdentity as b, Color4GradientList as b0, Pane as b1, TextureUpload as b2, BooleanBadgePropertyLine as b3, Color3PropertyLine as b4, Color4PropertyLine as b5, ComboBoxPropertyLine as b6, HexPropertyLine as b7, LinkPropertyLine as b8, PropertyLine as b9, LineContainer as ba, PlaceholderPropertyLine as bb, StringifiedPropertyLine as bc, SwitchPropertyLine as bd, SyncedSliderPropertyLine as be, TextAreaPropertyLine as bf, TextPropertyLine as bg, RotationVectorPropertyLine as bh, QuaternionPropertyLine as bi, Vector2PropertyLine as bj, Vector4PropertyLine as bk, SceneContextIdentity as c, SelectionServiceIdentity as d, useObservableState as e, AccordionSection as f, ButtonLine as g, ToolsServiceIdentity as h, useExtensionManager as i, Link as j, SidePaneContainer as k, PropertiesServiceIdentity as l, SceneExplorerServiceIdentity as m, SettingsServiceIdentity as n, StatsServiceIdentity as o, ThemeServiceIdentity as p, SettingsStoreIdentity as q, ConvertOptions as r, AttachDebugLayer as s, DetachDebugLayer as t, useToast as u, NumberDropdownPropertyLine as v, StringDropdownPropertyLine as w, BoundProperty as x, Property as y, LinkToEntityPropertyLine as z };
22690
+ //# sourceMappingURL=index-Ddfh2kw4.js.map