@babylonjs/inspector 8.52.0 → 8.52.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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';
@@ -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",
@@ -1425,7 +1441,7 @@ const useStyles$S = makeStyles({
1425
1441
  */
1426
1442
  const AccordionMenuBar = () => {
1427
1443
  AccordionMenuBar.displayName = "AccordionMenuBar";
1428
- const classes = useStyles$S();
1444
+ const classes = useStyles$T();
1429
1445
  const accordionCtx = useContext(AccordionContext);
1430
1446
  if (!accordionCtx) {
1431
1447
  return null;
@@ -1468,7 +1484,7 @@ const AccordionSectionBlock = (props) => {
1468
1484
  const AccordionSectionItem = (props) => {
1469
1485
  AccordionSectionItem.displayName = "AccordionSectionItem";
1470
1486
  const { children, staticItem } = props;
1471
- const classes = useStyles$S();
1487
+ const classes = useStyles$T();
1472
1488
  const accordionCtx = useContext(AccordionContext);
1473
1489
  const itemState = useAccordionSectionItemState(props);
1474
1490
  const [ctrlMode, setCtrlMode] = useState(false);
@@ -1508,7 +1524,7 @@ const AccordionSectionItem = (props) => {
1508
1524
  */
1509
1525
  const AccordionPinnedContainer = () => {
1510
1526
  AccordionPinnedContainer.displayName = "AccordionPinnedContainer";
1511
- const classes = useStyles$S();
1527
+ const classes = useStyles$T();
1512
1528
  const accordionCtx = useContext(AccordionContext);
1513
1529
  return (jsx("div", { ref: accordionCtx?.pinnedContainerRef, className: classes.pinnedContainer, children: jsx(MessageBar$1, { className: classes.pinnedContainerEmpty, children: jsx(MessageBarBody, { children: "No pinned items" }) }) }));
1514
1530
  };
@@ -1519,7 +1535,7 @@ const AccordionPinnedContainer = () => {
1519
1535
  */
1520
1536
  const AccordionSearchBox = () => {
1521
1537
  AccordionSearchBox.displayName = "AccordionSearchBox";
1522
- const classes = useStyles$S();
1538
+ const classes = useStyles$T();
1523
1539
  const accordionCtx = useContext(AccordionContext);
1524
1540
  if (!accordionCtx?.features.search) {
1525
1541
  return null;
@@ -1535,14 +1551,14 @@ const AccordionSearchBox = () => {
1535
1551
  */
1536
1552
  const AccordionSection = (props) => {
1537
1553
  AccordionSection.displayName = "AccordionSection";
1538
- const classes = useStyles$S();
1554
+ const classes = useStyles$T();
1539
1555
  return jsx("div", { className: classes.panelDiv, children: props.children });
1540
1556
  };
1541
1557
  const StringAccordion = Accordion$1;
1542
1558
  const Accordion = forwardRef((props, ref) => {
1543
1559
  Accordion.displayName = "Accordion";
1544
1560
  const { children, highlightSections, ...rest } = props;
1545
- const classes = useStyles$S();
1561
+ const classes = useStyles$T();
1546
1562
  const { size } = useContext(ToolContext);
1547
1563
  const accordionCtx = useAccordionContext(props);
1548
1564
  const hasPinning = accordionCtx?.features.pinning ?? false;
@@ -1639,7 +1655,7 @@ const Collapse = (props) => {
1639
1655
  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
1656
  };
1641
1657
 
1642
- const useStyles$R = makeStyles({
1658
+ const useStyles$S = makeStyles({
1643
1659
  button: {
1644
1660
  display: "flex",
1645
1661
  alignItems: "center",
@@ -1657,7 +1673,7 @@ const ToggleButton = (props) => {
1657
1673
  ToggleButton.displayName = "ToggleButton";
1658
1674
  const { value, onChange, title, appearance = "subtle" } = props;
1659
1675
  const { size } = useContext(ToolContext);
1660
- const classes = useStyles$R();
1676
+ const classes = useStyles$S();
1661
1677
  const [checked, setChecked] = useState(value);
1662
1678
  const toggle = useCallback(() => {
1663
1679
  setChecked((prevChecked) => {
@@ -1908,11 +1924,11 @@ const CompactModeSettingDescriptor = {
1908
1924
  };
1909
1925
  const UseDegreesSettingDescriptor = {
1910
1926
  key: "UseDegrees",
1911
- defaultValue: false,
1927
+ defaultValue: true,
1912
1928
  };
1913
1929
  const UseEulerSettingDescriptor = {
1914
1930
  key: "UseEuler",
1915
- defaultValue: false,
1931
+ defaultValue: true,
1916
1932
  };
1917
1933
  const DisableCopySettingDescriptor = {
1918
1934
  key: "DisableCopy",
@@ -1996,7 +2012,7 @@ const UXContextProvider = (props) => {
1996
2012
  function AsReadonlyArray(array) {
1997
2013
  return array;
1998
2014
  }
1999
- const useStyles$Q = makeStyles({
2015
+ const useStyles$R = makeStyles({
2000
2016
  rootDiv: {
2001
2017
  flex: 1,
2002
2018
  overflow: "hidden",
@@ -2005,7 +2021,7 @@ const useStyles$Q = makeStyles({
2005
2021
  },
2006
2022
  });
2007
2023
  function ExtensibleAccordion(props) {
2008
- const classes = useStyles$Q();
2024
+ const classes = useStyles$R();
2009
2025
  const { children, sections, sectionContent, context, sectionsRef, ...rest } = props;
2010
2026
  const defaultSections = useMemo(() => {
2011
2027
  const defaultSections = [];
@@ -2130,7 +2146,7 @@ function ExtensibleAccordion(props) {
2130
2146
  })] }) })) }));
2131
2147
  }
2132
2148
 
2133
- const useStyles$P = makeStyles({
2149
+ const useStyles$Q = makeStyles({
2134
2150
  paneRootDiv: {
2135
2151
  display: "flex",
2136
2152
  flex: 1,
@@ -2143,7 +2159,7 @@ const useStyles$P = makeStyles({
2143
2159
  */
2144
2160
  const SidePaneContainer = forwardRef((props, ref) => {
2145
2161
  const { className, ...rest } = props;
2146
- const classes = useStyles$P();
2162
+ const classes = useStyles$Q();
2147
2163
  return (jsx("div", { className: mergeClasses(classes.paneRootDiv, className), ref: ref, ...rest, children: props.children }));
2148
2164
  });
2149
2165
 
@@ -2393,7 +2409,7 @@ const Theme = (props) => {
2393
2409
  return (jsx(FluentProvider, { theme: theme, applyStylesToPortals: applyStylesToPortals, ...rest, children: props.children }));
2394
2410
  };
2395
2411
 
2396
- const useStyles$O = makeStyles({
2412
+ const useStyles$P = makeStyles({
2397
2413
  extensionTeachingPopover: {
2398
2414
  maxWidth: "320px",
2399
2415
  },
@@ -2404,7 +2420,7 @@ const useStyles$O = makeStyles({
2404
2420
  * @returns The teaching moment popover.
2405
2421
  */
2406
2422
  const TeachingMoment = ({ shouldDisplay, positioningRef, onOpenChange, title, description }) => {
2407
- const classes = useStyles$O();
2423
+ const classes = useStyles$P();
2408
2424
  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
2425
  };
2410
2426
 
@@ -2588,13 +2604,13 @@ function ConstructorFactory(constructor) {
2588
2604
  return (...args) => new constructor(...args);
2589
2605
  }
2590
2606
 
2591
- const useStyles$N = makeStyles({
2607
+ const useStyles$O = makeStyles({
2592
2608
  placeholderDiv: {
2593
2609
  padding: `${tokens.spacingVerticalM} ${tokens.spacingHorizontalM}`,
2594
2610
  },
2595
2611
  });
2596
2612
  const PropertiesPane = (props) => {
2597
- const classes = useStyles$N();
2613
+ const classes = useStyles$O();
2598
2614
  const entity = props.context;
2599
2615
  return entity != null ? (jsx(ExtensibleAccordion, { ...props })) : (jsx("div", { className: classes.placeholderDiv, children: jsx(Body1Strong, { italic: true, children: "No entity selected." }) }));
2600
2616
  };
@@ -2896,7 +2912,7 @@ const RightSidePaneHeightAdjustSettingDescriptor = {
2896
2912
  };
2897
2913
  const RootComponentServiceIdentity = Symbol("RootComponent");
2898
2914
  const ShellServiceIdentity = Symbol("ShellService");
2899
- const useStyles$M = makeStyles({
2915
+ const useStyles$N = makeStyles({
2900
2916
  mainView: {
2901
2917
  flex: 1,
2902
2918
  display: "flex",
@@ -3099,34 +3115,38 @@ const DockMenu = (props) => {
3099
3115
  };
3100
3116
  const PaneHeader = (props) => {
3101
3117
  const { id, title, dockOptions } = props;
3102
- const classes = useStyles$M();
3118
+ const classes = useStyles$N();
3103
3119
  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
3120
  };
3105
3121
  // 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();
3122
+ const ToolbarItem = (props) => {
3123
+ // eslint-disable-next-line @typescript-eslint/naming-convention
3124
+ const { verticalLocation, horizontalLocation, id, component: Component, displayName } = props;
3125
+ const classes = useStyles$N();
3108
3126
  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, {}) })] }));
3127
+ const teachingMoment = useTeachingMoment(props.teachingMoment === false);
3128
+ const title = typeof props.teachingMoment === "object" ? props.teachingMoment.title : (displayName ?? id);
3129
+ const description = typeof props.teachingMoment === "object" ? props.teachingMoment.description : `The "${displayName ?? id}" extension can be accessed here.`;
3130
+ 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
3131
  };
3112
3132
  // TODO: Handle overflow, possibly via https://react.fluentui.dev/?path=/docs/components-overflow--docs with priority.
3113
3133
  // This component just renders a toolbar with left aligned toolbar items on the left and right aligned toolbar items on the right.
3114
3134
  const Toolbar = ({ location, components }) => {
3115
- const classes = useStyles$M();
3135
+ const classes = useStyles$N();
3116
3136
  const leftComponents = useMemo(() => components.filter((entry) => entry.horizontalLocation === "left"), [components]);
3117
3137
  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))) })] })) }));
3138
+ 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
3139
  };
3120
3140
  // 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
3141
  const SidePaneTab = (props) => {
3122
3142
  const { location, id, isSelected, isFirst, isLast, dockOptions,
3123
3143
  // eslint-disable-next-line @typescript-eslint/naming-convention
3124
- icon: Icon, title, suppressTeachingMoment, } = props;
3125
- const classes = useStyles$M();
3144
+ icon: Icon, title, } = props;
3145
+ const classes = useStyles$N();
3126
3146
  const useTeachingMoment = useMemo(() => MakePopoverTeachingMoment(`Pane/${location}/${title ?? id}`), [title, id]);
3127
- const teachingMoment = useTeachingMoment(suppressTeachingMoment);
3147
+ const teachingMoment = useTeachingMoment(props.teachingMoment === false);
3128
3148
  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: {
3149
+ 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
3150
  children: jsx(Icon, {}),
3131
3151
  } }) }) }) })] }));
3132
3152
  };
@@ -3134,7 +3154,7 @@ const SidePaneTab = (props) => {
3134
3154
  // In "compact" mode, the tab list is integrated into the pane itself.
3135
3155
  // In "full" mode, the returned tab list is later injected into the toolbar.
3136
3156
  function usePane(location, defaultWidth, minWidth, sidePanes, onSelectSidePane, dockOperations, toolbarMode, topBarItems, bottomBarItems, initialCollapsed) {
3137
- const classes = useStyles$M();
3157
+ const classes = useStyles$N();
3138
3158
  const [topSelectedTab, setTopSelectedTab] = useState();
3139
3159
  const [bottomSelectedTab, setBottomSelectedTab] = useState();
3140
3160
  const [collapsed, setCollapsed] = useState(initialCollapsed);
@@ -3236,7 +3256,7 @@ function usePane(location, defaultWidth, minWidth, sidePanes, onSelectSidePane,
3236
3256
  setCollapsed(false);
3237
3257
  }, children: paneComponents.map((entry, index) => {
3238
3258
  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));
3259
+ 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
3260
  }) }) })), toolbarMode === "full" && (jsx(Collapse, { visible: !isChildWindowOpen, orientation: "horizontal", children: expandCollapseButton }))] })) }));
3241
3261
  }, [location, collapsed, isChildWindowOpen, expandCollapseButton]);
3242
3262
  // 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 +3351,7 @@ function MakeShellServiceDefinition({ leftPaneDefaultWidth = 350, leftPaneMinWid
3331
3351
  expand: () => onCollapseChanged.notifyObservers({ location: "right", collapsed: false }),
3332
3352
  };
3333
3353
  const rootComponent = () => {
3334
- const classes = useStyles$M();
3354
+ const classes = useStyles$N();
3335
3355
  const [sidePaneDockOverrides, setSidePaneDockOverrides] = useSetting(SidePaneDockOverridesSettingDescriptor);
3336
3356
  // This function returns a promise that resolves after the dock change takes effect so that
3337
3357
  // we can then select the re-docked pane.
@@ -3566,7 +3586,7 @@ const SettingsServiceDefinition = {
3566
3586
  horizontalLocation: "right",
3567
3587
  verticalLocation: "top",
3568
3588
  order: 500,
3569
- suppressTeachingMoment: true,
3589
+ teachingMoment: false,
3570
3590
  content: () => {
3571
3591
  const sections = useOrderedObservableCollection(sectionsCollection);
3572
3592
  const sectionContent = useObservableCollection(sectionContentCollection);
@@ -3672,7 +3692,7 @@ const PropertiesServiceDefinition = {
3672
3692
  horizontalLocation: "right",
3673
3693
  verticalLocation: "top",
3674
3694
  order: 100,
3675
- suppressTeachingMoment: true,
3695
+ teachingMoment: false,
3676
3696
  keepMounted: true,
3677
3697
  content: () => {
3678
3698
  const sections = useOrderedObservableCollection(sectionsCollection);
@@ -4076,7 +4096,7 @@ function CoerceEntityArray(entities, sort) {
4076
4096
  }
4077
4097
  return entities;
4078
4098
  }
4079
- const useStyles$L = makeStyles({
4099
+ const useStyles$M = makeStyles({
4080
4100
  rootDiv: {
4081
4101
  flex: 1,
4082
4102
  overflow: "hidden",
@@ -4184,14 +4204,14 @@ function MakeInlineCommandElement(command, isPlaceholder) {
4184
4204
  }
4185
4205
  const SceneTreeItem = (props) => {
4186
4206
  const { isSelected, select } = props;
4187
- const classes = useStyles$L();
4207
+ const classes = useStyles$M();
4188
4208
  const [compactMode] = useSetting(CompactModeSettingDescriptor);
4189
4209
  const treeItemLayoutClass = mergeClasses(classes.sceneTreeItemLayout, compactMode ? classes.treeItemLayoutCompact : undefined);
4190
4210
  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
4211
  };
4192
4212
  const SectionTreeItem = (props) => {
4193
4213
  const { section, isFiltering, commandProviders, expandAll, collapseAll, isDropTarget, ...dropProps } = props;
4194
- const classes = useStyles$L();
4214
+ const classes = useStyles$M();
4195
4215
  const [compactMode] = useSetting(CompactModeSettingDescriptor);
4196
4216
  // Get the commands that apply to this section.
4197
4217
  const commands = useResource(useCallback(() => {
@@ -4208,7 +4228,7 @@ const SectionTreeItem = (props) => {
4208
4228
  };
4209
4229
  const EntityTreeItem = (props) => {
4210
4230
  const { entityItem, isSelected, select, isFiltering, commandProviders, expandAll, collapseAll, isDragging, isDropTarget, ...dragProps } = props;
4211
- const classes = useStyles$L();
4231
+ const classes = useStyles$M();
4212
4232
  const [compactMode] = useSetting(CompactModeSettingDescriptor);
4213
4233
  const hasChildren = !!entityItem.children?.length;
4214
4234
  const displayInfo = useResource(useCallback(() => {
@@ -4324,7 +4344,7 @@ const EntityTreeItem = (props) => {
4324
4344
  }, 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
4345
  };
4326
4346
  const SceneExplorer = (props) => {
4327
- const classes = useStyles$L();
4347
+ const classes = useStyles$M();
4328
4348
  const { sections, entityCommandProviders, sectionCommandProviders, scene, selectedEntity = null } = props;
4329
4349
  const [openItems, setOpenItems] = useState(new Set());
4330
4350
  const [sceneVersion, setSceneVersion] = useState(0);
@@ -4617,7 +4637,7 @@ const SceneExplorerServiceDefinition = {
4617
4637
  icon: CubeTreeRegular,
4618
4638
  horizontalLocation: "left",
4619
4639
  verticalLocation: "top",
4620
- suppressTeachingMoment: true,
4640
+ teachingMoment: false,
4621
4641
  keepMounted: true,
4622
4642
  content: () => {
4623
4643
  const sections = useOrderedObservableCollection(sectionsCollection);
@@ -4778,7 +4798,7 @@ const DebugServiceDefinition = {
4778
4798
  horizontalLocation: "right",
4779
4799
  verticalLocation: "top",
4780
4800
  order: 200,
4781
- suppressTeachingMoment: true,
4801
+ teachingMoment: false,
4782
4802
  content: () => {
4783
4803
  const sections = useOrderedObservableCollection(sectionsCollection);
4784
4804
  const sectionContent = useObservableCollection(sectionContentCollection);
@@ -5947,7 +5967,7 @@ class CanvasGraphService {
5947
5967
  }
5948
5968
  }
5949
5969
 
5950
- const useStyles$K = makeStyles({
5970
+ const useStyles$L = makeStyles({
5951
5971
  canvas: {
5952
5972
  flexGrow: 1,
5953
5973
  width: "100%",
@@ -5956,7 +5976,7 @@ const useStyles$K = makeStyles({
5956
5976
  });
5957
5977
  const CanvasGraph = (props) => {
5958
5978
  const { collector, scene, layoutObservable, returnToPlayheadObservable, onVisibleRangeChangedObservable, initialGraphSize } = props;
5959
- const classes = useStyles$K();
5979
+ const classes = useStyles$L();
5960
5980
  const canvasRef = useRef(null);
5961
5981
  useEffect(() => {
5962
5982
  if (!canvasRef.current) {
@@ -6010,135 +6030,208 @@ const CanvasGraph = (props) => {
6010
6030
  return jsx("canvas", { className: classes.canvas, ref: canvasRef });
6011
6031
  };
6012
6032
 
6013
- function CoerceStepValue(step, isAltKeyPressed, isShiftKeyPressed) {
6014
- // When the alt key is pressed, decrease step by a factor of 10.
6015
- if (isAltKeyPressed) {
6033
+ function CoerceStepValue(step, isFineKeyPressed, isCourseKeyPressed) {
6034
+ // When the fine key is pressed, decrease step by a factor of 10.
6035
+ if (isFineKeyPressed) {
6016
6036
  return step * 0.1;
6017
6037
  }
6018
- // When the shift key is pressed, increase step by a factor of 10.
6019
- if (isShiftKeyPressed) {
6038
+ // When the course key is pressed, increase step by a factor of 10.
6039
+ if (isCourseKeyPressed) {
6020
6040
  return step * 10;
6021
6041
  }
6022
6042
  return step;
6023
6043
  }
6044
+ // Allow arbitrary expressions, primarily for math operations (e.g. 10*60 for 10 minutes in seconds).
6045
+ // Use Function constructor to safely evaluate the expression without allowing access to scope.
6046
+ // If the expression is invalid, fallback to NaN which will be caught by validateValue and prevent committing.
6047
+ function EvaluateExpression(rawValue) {
6048
+ const val = rawValue.trim();
6049
+ try {
6050
+ return Number(Function(`"use strict";return (${val})`)());
6051
+ }
6052
+ catch {
6053
+ return NaN;
6054
+ }
6055
+ }
6056
+ const useStyles$K = makeStyles({
6057
+ icon: {
6058
+ "&:hover": {
6059
+ color: tokens.colorBrandForeground1,
6060
+ },
6061
+ },
6062
+ });
6063
+ /**
6064
+ * A numeric input with a vertical drag-to-scrub icon (ArrowsBidirectionalRegular rotated 90°).
6065
+ * Click-and-drag up/down on the icon to increment/decrement the value.
6066
+ */
6024
6067
  const SpinButton = forwardRef((props, ref) => {
6025
- SpinButton.displayName = "SpinButton";
6026
- const classes = useInputStyles$1();
6068
+ SpinButton.displayName = "SpinButton2";
6069
+ const inputClasses = useInputStyles$1();
6070
+ const classes = useStyles$K();
6027
6071
  const { size } = useContext(ToolContext);
6028
6072
  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);
6073
+ const baseStep = props.step ?? 1;
6074
+ // Local ref for the input element so we can blur it programmatically (e.g. when a drag starts while editing).
6075
+ const inputRef = useRef(null);
6076
+ const mergedRef = useMergedRefs(ref, inputRef);
6077
+ // Modifier keys for step coercion.
6078
+ const isAltKeyPressed = useKeyState("Alt", { preventDefault: true });
6079
+ const isShiftKeyPressed = useKeyState("Shift");
6080
+ const step = CoerceStepValue(baseStep, isAltKeyPressed, isShiftKeyPressed);
6039
6081
  const stepPrecision = Math.max(0, CalculatePrecision(step));
6040
- 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;
6046
- useEffect(() => {
6047
- if (props.value !== lastCommittedValue.current) {
6048
- lastCommittedValue.current = props.value;
6049
- setValue(props.value); // Update local state when props.value changes
6082
+ const [value, setValue] = useState(props.value ?? 0);
6083
+ const lastCommittedValue = useRef(props.value);
6084
+ const [isDragging, setIsDragging] = useState(false);
6085
+ const scrubStartYRef = useRef(0);
6086
+ const scrubStartValueRef = useRef(0);
6087
+ const lastPointerYRef = useRef(0);
6088
+ const [isHovered, setIsHovered] = useState(false);
6089
+ // Editing state: when the user is typing, we show their raw text rather than the formatted value.
6090
+ const [isEditing, setIsEditing] = useState(false);
6091
+ const [editText, setEditText] = useState("");
6092
+ const valuePrecision = Math.max(0, CalculatePrecision(value));
6093
+ // Display precision: controls how many decimals are shown in the formatted displayValue. Cap at 4 to avoid wild numbers.
6094
+ // If a fixed precision prop is provided, use it instead.
6095
+ const displayPrecision = props.precision ?? Math.min(4, Math.max(stepPrecision, valuePrecision));
6096
+ // Format a number for display: toFixed, then trim trailing zeros and period unless a fixed precision is specified.
6097
+ const formatValue = useCallback((v) => {
6098
+ const fixed = v.toFixed(displayPrecision);
6099
+ if (props.precision !== undefined) {
6100
+ return fixed;
6101
+ }
6102
+ return fixed.replace(/(\.\d*?)0+$/, "$1").replace(/\.$/, "");
6103
+ }, [displayPrecision, props.precision]);
6104
+ useEffect(() => {
6105
+ if (!isDragging && props.value !== lastCommittedValue.current) {
6106
+ lastCommittedValue.current = props.value;
6107
+ setValue(props.value ?? 0);
6050
6108
  }
6051
- }, [props.value]);
6052
- const validateValue = (numericValue) => {
6109
+ }, [props.value, isDragging]);
6110
+ const validateValue = useCallback((numericValue) => {
6053
6111
  const outOfBounds = (min !== undefined && numericValue < min) || (max !== undefined && numericValue > max);
6054
6112
  const failsValidator = props.validator && !props.validator(numericValue);
6055
6113
  const failsIntCheck = props.forceInt ? !Number.isInteger(numericValue) : false;
6056
6114
  const invalid = !!outOfBounds || !!failsValidator || isNaN(numericValue) || !!failsIntCheck;
6057
6115
  return !invalid;
6058
- };
6059
- const tryCommitValue = (currVal) => {
6060
- // Only commit if valid and different from last committed value
6116
+ }, [min, max, props.validator, props.forceInt]);
6117
+ // Constrain a value to the valid range by clamping to [min, max].
6118
+ const constrainValue = useCallback((v) => Clamp(v, min ?? -Infinity, max ?? Infinity), [min, max]);
6119
+ const tryCommitValue = useCallback((currVal) => {
6061
6120
  if (validateValue(currVal) && currVal !== lastCommittedValue.current) {
6062
6121
  lastCommittedValue.current = currVal;
6063
6122
  props.onChange(currVal);
6064
6123
  }
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;
6124
+ }, [validateValue, props.onChange]);
6125
+ const handleInputChange = useCallback((_, data) => {
6126
+ // Just update the raw text — no evaluation or commit until Enter/blur.
6127
+ setEditText(data.value);
6128
+ }, []);
6129
+ // Evaluate the current edit text and commit the value. Returns the clamped value if valid, or undefined.
6130
+ const commitEditText = useCallback((text) => {
6131
+ const numericValue = EvaluateExpression(text);
6132
+ if (!isNaN(numericValue) && validateValue(numericValue)) {
6133
+ const constrained = constrainValue(numericValue);
6134
+ setValue(constrained);
6135
+ tryCommitValue(constrained);
6136
+ return constrained;
6095
6137
  }
6096
- };
6097
- const handleKeyDown = (event) => {
6098
- if (event.key === "Alt") {
6099
- setIsFocusedAltKeyPressed(true);
6138
+ return undefined;
6139
+ }, [validateValue, constrainValue, tryCommitValue]);
6140
+ const handleIconPointerDown = useCallback((e) => {
6141
+ e.preventDefault();
6142
+ e.stopPropagation();
6143
+ // If the input was being edited, commit the current text and blur the input
6144
+ // so the focus state stays consistent after the drag ends.
6145
+ let startValue = value;
6146
+ if (isEditing) {
6147
+ const committed = commitEditText(editText);
6148
+ if (committed !== undefined) {
6149
+ startValue = committed;
6150
+ }
6151
+ setIsEditing(false);
6152
+ }
6153
+ // Blur the active element to ensure we can observe document level modifier keys.
6154
+ inputRef.current?.ownerDocument.activeElement?.blur?.();
6155
+ setIsDragging(true);
6156
+ scrubStartYRef.current = e.clientY;
6157
+ scrubStartValueRef.current = startValue;
6158
+ e.currentTarget.setPointerCapture(e.pointerId);
6159
+ }, [value, isEditing, editText, commitEditText]);
6160
+ // When the step size changes during a drag (e.g. Shift/Alt pressed or released), reset the scrub reference point
6161
+ // to the current value and pointer position so only future movement uses the new step.
6162
+ useEffect(() => {
6163
+ if (isDragging) {
6164
+ scrubStartValueRef.current = value;
6165
+ scrubStartYRef.current = lastPointerYRef.current;
6100
6166
  }
6101
- else if (event.key === "Shift") {
6102
- setIsFocusedShiftKeyPressed(true);
6167
+ }, [step]);
6168
+ const handleIconPointerMove = useCallback((e) => {
6169
+ if (!isDragging) {
6170
+ return;
6103
6171
  }
6104
- // Evaluate on Enter in keyDown (before Fluent's internal commit clears the raw text
6105
- // and re-renders with the truncated displayValue).
6172
+ lastPointerYRef.current = e.clientY;
6173
+ // Dragging up (negative dy) should increment, dragging down should decrement.
6174
+ // Scale delta by step but round to display precision (not step) for smooth fine-grained control.
6175
+ const dy = scrubStartYRef.current - e.clientY;
6176
+ // 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.
6177
+ const delta = (dy * step) / 5;
6178
+ const raw = scrubStartValueRef.current + delta;
6179
+ const precisionFactor = Math.pow(10, displayPrecision);
6180
+ const rounded = Math.round(raw * precisionFactor) / precisionFactor;
6181
+ const constrained = constrainValue(rounded);
6182
+ setValue(constrained);
6183
+ tryCommitValue(constrained);
6184
+ }, [isDragging, step, displayPrecision, constrainValue, tryCommitValue]);
6185
+ const handleIconPointerUp = useCallback((e) => {
6186
+ setIsDragging(false);
6187
+ e.currentTarget.releasePointerCapture(e.pointerId);
6188
+ }, []);
6189
+ const handleKeyDown = useCallback((event) => {
6190
+ // Commit on Enter and blur the input if the value is valid.
6106
6191
  if (event.key === "Enter") {
6107
- const currVal = evaluateExpression(event.target.value);
6108
- if (!isNaN(currVal)) {
6109
- setValue(currVal);
6110
- tryCommitValue(currVal);
6192
+ const committed = commitEditText(event.currentTarget.value);
6193
+ if (committed !== undefined) {
6194
+ inputRef.current?.blur();
6111
6195
  }
6112
6196
  }
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);
6197
+ if (event.key === "ArrowUp" || event.key === "ArrowDown") {
6198
+ event.preventDefault();
6199
+ const direction = event.key === "ArrowUp" ? 1 : -1;
6200
+ const newValue = constrainValue(Math.round((value + direction * step) / step) * step);
6201
+ setValue(newValue);
6202
+ tryCommitValue(newValue);
6203
+ // Update edit text to reflect the new value so the user sees the change
6204
+ setEditText(formatValue(newValue));
6122
6205
  }
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") {
6206
+ HandleKeyDown(event);
6207
+ }, [value, step, constrainValue, tryCommitValue, commitEditText, formatValue]);
6208
+ const id = useId("spin-button2");
6209
+ // Real-time validation: when editing, validate the expression; otherwise validate the committed value.
6210
+ // (validateValue already handles NaN, so no separate isNaN check needed.)
6211
+ const isInputInvalid = !validateValue(isEditing ? EvaluateExpression(editText) : value);
6212
+ const mergedClassName = mergeClasses(inputClasses.input, isInputInvalid ? inputClasses.invalid : "", props.className);
6213
+ const inputSlotClassName = mergeClasses(inputClasses.inputSlot, props.inputClassName);
6214
+ const formattedValue = formatValue(value);
6215
+ const handleFocus = useCallback(() => {
6216
+ setIsEditing(true);
6217
+ setEditText(formattedValue);
6218
+ }, [formattedValue]);
6219
+ const handleBlur = useCallback((event) => {
6220
+ // Skip blur handling if a drag just started (icon pointerDown already committed).
6221
+ if (isDragging) {
6126
6222
  return;
6127
6223
  }
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);
6224
+ commitEditText(event.target.value);
6225
+ setIsEditing(false);
6226
+ HandleOnBlur(event);
6227
+ }, [commitEditText, isDragging]);
6228
+ 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;
6229
+ const input = (jsx("div", { onMouseEnter: () => setIsHovered(true), onMouseLeave: () => {
6230
+ if (!isDragging) {
6231
+ setIsHovered(false);
6232
+ }
6233
+ }, 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 }) }));
6234
+ return props.infoLabel ? (jsxs("div", { className: inputClasses.container, children: [jsx(InfoLabel, { ...props.infoLabel, htmlFor: id }), input] })) : (input);
6142
6235
  });
6143
6236
 
6144
6237
  const TextInput = (props) => {
@@ -6820,7 +6913,7 @@ const StatsServiceDefinition = {
6820
6913
  horizontalLocation: "right",
6821
6914
  verticalLocation: "top",
6822
6915
  order: 300,
6823
- suppressTeachingMoment: true,
6916
+ teachingMoment: false,
6824
6917
  content: () => {
6825
6918
  const sections = useOrderedObservableCollection(sectionsCollection);
6826
6919
  const sectionContent = useObservableCollection(sectionContentCollection);
@@ -6909,7 +7002,7 @@ const ToolsServiceDefinition = {
6909
7002
  horizontalLocation: "right",
6910
7003
  verticalLocation: "top",
6911
7004
  order: 400,
6912
- suppressTeachingMoment: true,
7005
+ teachingMoment: false,
6913
7006
  content: () => {
6914
7007
  const sections = useOrderedObservableCollection(sectionsCollection);
6915
7008
  const sectionContent = useObservableCollection(sectionContentCollection);
@@ -6927,12 +7020,337 @@ const ToolsServiceDefinition = {
6927
7020
  },
6928
7021
  };
6929
7022
 
7023
+ const useStyles$F = makeStyles({
7024
+ dropdown: {
7025
+ ...UniformWidthStyling,
7026
+ },
7027
+ });
7028
+ /**
7029
+ * Wraps a dropdown in a property line
7030
+ * @param props - PropertyLineProps and DropdownProps
7031
+ * @returns property-line wrapped dropdown
7032
+ */
7033
+ const DropdownPropertyLine = forwardRef((props, ref) => {
7034
+ DropdownPropertyLine.displayName = "DropdownPropertyLine";
7035
+ const classes = useStyles$F();
7036
+ return (jsx(PropertyLine, { ...props, ref: ref, children: jsx(Dropdown, { ...props, className: classes.dropdown }) }));
7037
+ });
7038
+ /**
7039
+ * Dropdown component for number values.
7040
+ */
7041
+ const NumberDropdownPropertyLine = DropdownPropertyLine;
7042
+ /**
7043
+ * Dropdown component for string values
7044
+ */
7045
+ const StringDropdownPropertyLine = DropdownPropertyLine;
7046
+
7047
+ /**
7048
+ * A slider primitive that wraps the Fluent UI Slider with step scaling, drag tracking, and optional notify-on-release behavior.
7049
+ * Follows the same pattern as other primitives (e.g. Switch) — no wrapper divs, just the Fluent component with logic.
7050
+ * @param props
7051
+ * @returns Slider component
7052
+ */
7053
+ const Slider = (props) => {
7054
+ Slider.displayName = "Slider";
7055
+ const { size } = useContext(ToolContext);
7056
+ const [value, setValue] = useState(props.value ?? 0);
7057
+ const pendingValueRef = useRef(undefined);
7058
+ const isDraggingRef = useRef(false);
7059
+ // NOTE: The Fluent slider will add tick marks if the step prop is anything other than undefined.
7060
+ // To avoid this, we scale the min/max based on the step so we can always make step undefined.
7061
+ // The actual step size in the Fluent slider is 1 when it is set to undefined.
7062
+ const min = props.min ?? 0;
7063
+ const max = props.max ?? 100;
7064
+ const step = props.step ?? 1;
7065
+ useEffect(() => {
7066
+ !isDraggingRef.current && setValue(props.value ?? 0); // Update local state when props.value changes as long as user is not actively dragging
7067
+ }, [props.value]);
7068
+ const handleSliderChange = (_, data) => {
7069
+ const newValue = data.value * step;
7070
+ setValue(newValue);
7071
+ if (props.notifyOnlyOnRelease) {
7072
+ // Store the value but don't notify parent yet
7073
+ pendingValueRef.current = newValue;
7074
+ }
7075
+ else {
7076
+ // Notify parent as slider changes
7077
+ props.onChange(newValue);
7078
+ }
7079
+ };
7080
+ const handleSliderPointerDown = () => {
7081
+ isDraggingRef.current = true;
7082
+ };
7083
+ const handleSliderPointerUp = () => {
7084
+ if (props.notifyOnlyOnRelease && isDraggingRef.current && pendingValueRef.current !== undefined) {
7085
+ props.onChange(pendingValueRef.current);
7086
+ pendingValueRef.current = undefined;
7087
+ }
7088
+ isDraggingRef.current = false;
7089
+ };
7090
+ 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: () => {
7091
+ handleSliderPointerDown();
7092
+ props.onPointerDown?.();
7093
+ }, onPointerUp: () => {
7094
+ handleSliderPointerUp();
7095
+ props.onPointerUp?.();
7096
+ } }));
7097
+ };
7098
+
7099
+ const useSyncedSliderStyles = makeStyles({
7100
+ container: { display: "flex", minWidth: 0 },
7101
+ syncedSlider: {
7102
+ flex: "1 1 0",
7103
+ flexDirection: "row",
7104
+ display: "flex",
7105
+ alignItems: "center",
7106
+ minWidth: 0,
7107
+ },
7108
+ slider: {
7109
+ flex: "1 1 auto",
7110
+ minWidth: "75px",
7111
+ maxWidth: "75px",
7112
+ },
7113
+ compactSlider: {
7114
+ flex: "1 1 auto",
7115
+ minWidth: "50px", // Allow shrinking for compact mode
7116
+ maxWidth: "75px",
7117
+ },
7118
+ growSlider: {
7119
+ flex: "1 1 auto",
7120
+ minWidth: "50px",
7121
+ // No maxWidth - slider grows to fill available space
7122
+ },
7123
+ compactSpinButton: {
7124
+ width: "65px",
7125
+ minWidth: "65px",
7126
+ maxWidth: "65px",
7127
+ },
7128
+ compactSpinButtonInput: {
7129
+ minWidth: "0",
7130
+ },
7131
+ });
7132
+ /**
7133
+ * Component which synchronizes a slider and an input field, allowing the user to change the value using either control
7134
+ * @param props
7135
+ * @returns SyncedSlider component
7136
+ */
7137
+ const SyncedSliderInput = (props) => {
7138
+ SyncedSliderInput.displayName = "SyncedSliderInput";
7139
+ const { infoLabel, ...passthroughProps } = props;
7140
+ const classes = useSyncedSliderStyles();
7141
+ const [value, setValue] = useState(props.value ?? 0);
7142
+ const pendingValueRef = useRef(undefined);
7143
+ const isDraggingRef = useRef(false);
7144
+ useEffect(() => {
7145
+ !isDraggingRef.current && setValue(props.value ?? 0); // Update local state when props.value changes as long as user is not actively dragging
7146
+ }, [props.value]);
7147
+ const handleSliderChange = (newValue) => {
7148
+ setValue(newValue);
7149
+ if (props.notifyOnlyOnRelease) {
7150
+ // Store the value but don't notify parent yet
7151
+ pendingValueRef.current = newValue;
7152
+ }
7153
+ else {
7154
+ // Notify parent as slider changes
7155
+ props.onChange(newValue);
7156
+ }
7157
+ };
7158
+ const handleSliderPointerDown = () => {
7159
+ isDraggingRef.current = true;
7160
+ };
7161
+ const handleSliderPointerUp = () => {
7162
+ if (props.notifyOnlyOnRelease && isDraggingRef.current && pendingValueRef.current !== undefined) {
7163
+ props.onChange(pendingValueRef.current);
7164
+ pendingValueRef.current = undefined;
7165
+ }
7166
+ isDraggingRef.current = false;
7167
+ };
7168
+ const handleInputChange = (value) => {
7169
+ setValue(value);
7170
+ props.onChange(value); // Input always updates immediately
7171
+ };
7172
+ const hasSlider = props.min !== undefined && props.max !== undefined;
7173
+ // Determine Slider className based on props
7174
+ const getSliderClassName = () => {
7175
+ if (props.growSlider) {
7176
+ return classes.growSlider;
7177
+ }
7178
+ if (props.compact) {
7179
+ return classes.compactSlider;
7180
+ }
7181
+ return classes.slider;
7182
+ };
7183
+ 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 })] })] }));
7184
+ };
7185
+
7186
+ /**
7187
+ * Renders a simple wrapper around the SyncedSliderInput
7188
+ * @param props
7189
+ * @returns
7190
+ */
7191
+ const SyncedSliderPropertyLine = forwardRef((props, ref) => {
7192
+ SyncedSliderPropertyLine.displayName = "SyncedSliderPropertyLine";
7193
+ const { label, description, ...sliderProps } = props;
7194
+ return (jsx(PropertyLine, { ref: ref, ...props, children: jsx(SyncedSliderInput, { ...sliderProps }) }));
7195
+ });
7196
+
7197
+ const WatcherSettingDescriptor = {
7198
+ key: "WatcherSettings",
7199
+ defaultValue: {
7200
+ mode: "intercept",
7201
+ },
7202
+ };
7203
+ const WatcherServiceIdentity = Symbol("WatcherService");
7204
+ const WatcherServiceDefinition = {
7205
+ friendlyName: "Watcher Service",
7206
+ produces: [WatcherServiceIdentity],
7207
+ consumes: [SettingsStoreIdentity],
7208
+ factory: (settingsStore) => {
7209
+ let refreshObservable = null;
7210
+ let pollingHandle = null;
7211
+ const applySettings = () => {
7212
+ const settings = settingsStore.readSetting(WatcherSettingDescriptor);
7213
+ if (pollingHandle !== null) {
7214
+ clearInterval(pollingHandle);
7215
+ pollingHandle = null;
7216
+ }
7217
+ if (settings.mode === "intercept") {
7218
+ if (refreshObservable) {
7219
+ refreshObservable.clear();
7220
+ refreshObservable = null;
7221
+ }
7222
+ }
7223
+ else {
7224
+ const pollingObservable = refreshObservable ?? (refreshObservable = new Observable());
7225
+ if (settings.mode === "polling") {
7226
+ pollingHandle = window.setInterval(() => {
7227
+ pollingObservable.notifyObservers();
7228
+ }, settings.interval);
7229
+ }
7230
+ }
7231
+ };
7232
+ const settingsStoreObserver = settingsStore.onChanged.add((key) => {
7233
+ if (key === WatcherSettingDescriptor.key) {
7234
+ applySettings();
7235
+ }
7236
+ });
7237
+ applySettings();
7238
+ return {
7239
+ watchProperty(target, propertyKey, onChanged) {
7240
+ if (refreshObservable) {
7241
+ let previousValue = target[propertyKey];
7242
+ const observer = refreshObservable.add(() => {
7243
+ const currentValue = target[propertyKey];
7244
+ if (!Object.is(previousValue, currentValue)) {
7245
+ previousValue = currentValue;
7246
+ onChanged(currentValue);
7247
+ }
7248
+ });
7249
+ return {
7250
+ dispose: () => observer.remove(),
7251
+ };
7252
+ }
7253
+ else {
7254
+ return InterceptProperty(target, propertyKey, {
7255
+ afterSet: (value) => onChanged(value),
7256
+ });
7257
+ }
7258
+ },
7259
+ refresh: () => {
7260
+ refreshObservable?.notifyObservers();
7261
+ },
7262
+ dispose: () => {
7263
+ if (pollingHandle !== null) {
7264
+ clearInterval(pollingHandle);
7265
+ pollingHandle = null;
7266
+ }
7267
+ refreshObservable?.clear();
7268
+ refreshObservable = null;
7269
+ settingsStoreObserver.remove();
7270
+ },
7271
+ };
7272
+ },
7273
+ };
7274
+ const WatchModes = [
7275
+ { label: "Interception", value: "intercept" },
7276
+ { label: "Polling", value: "polling" },
7277
+ { label: "Manual", value: "manual" },
7278
+ ];
7279
+ const WatcherSettingsServiceDefinition = {
7280
+ friendlyName: "Watcher Settings Service",
7281
+ consumes: [SettingsServiceIdentity],
7282
+ factory: (settingsService) => {
7283
+ const settingsRegistration = settingsService.addSectionContent({
7284
+ key: "watcherSettings",
7285
+ section: "UI",
7286
+ component: () => {
7287
+ const [watcherSettings, setWatcherSettings] = useSetting(WatcherSettingDescriptor);
7288
+ 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) => {
7289
+ return { interval: 250, ...prev, mode: value };
7290
+ }) }), 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) => {
7291
+ return { ...prev, interval: value };
7292
+ }) }) })] }));
7293
+ },
7294
+ });
7295
+ return {
7296
+ dispose: () => {
7297
+ settingsRegistration.dispose();
7298
+ },
7299
+ };
7300
+ },
7301
+ };
7302
+ const WatcherRefreshToolbarServiceDefinition = {
7303
+ friendlyName: "Watcher Refresh Toolbar Service",
7304
+ consumes: [WatcherServiceIdentity, SettingsStoreIdentity, ShellServiceIdentity],
7305
+ factory: (watcherService, settingsStore, shellService) => {
7306
+ let toolbarItemRegistration = null;
7307
+ const updateToolbar = () => {
7308
+ const settings = settingsStore.readSetting(WatcherSettingDescriptor);
7309
+ if (settings.mode === "manual") {
7310
+ if (!toolbarItemRegistration) {
7311
+ toolbarItemRegistration = shellService.addToolbarItem({
7312
+ key: "Watcher Refresh",
7313
+ displayName: "Refresh Properties",
7314
+ verticalLocation: "bottom",
7315
+ horizontalLocation: "right",
7316
+ order: 200 /* DefaultToolbarItemOrder.RefreshProperties */,
7317
+ teachingMoment: {
7318
+ title: "Refresh Properties",
7319
+ 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.",
7320
+ },
7321
+ component: () => {
7322
+ 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() }));
7323
+ },
7324
+ });
7325
+ }
7326
+ }
7327
+ else {
7328
+ toolbarItemRegistration?.dispose();
7329
+ toolbarItemRegistration = null;
7330
+ }
7331
+ };
7332
+ updateToolbar();
7333
+ const settingsStoreObserver = settingsStore.onChanged.add((key) => {
7334
+ if (key === WatcherSettingDescriptor.key) {
7335
+ updateToolbar();
7336
+ }
7337
+ });
7338
+ return {
7339
+ dispose: () => {
7340
+ toolbarItemRegistration?.dispose();
7341
+ toolbarItemRegistration = null;
7342
+ settingsStoreObserver.remove();
7343
+ },
7344
+ };
7345
+ },
7346
+ };
7347
+
6930
7348
  const GizmoServiceIdentity = Symbol("GizmoService");
6931
7349
  const GizmoServiceDefinition = {
6932
7350
  friendlyName: "Gizmo Service",
6933
7351
  produces: [GizmoServiceIdentity],
6934
- consumes: [SceneContextIdentity, SelectionServiceIdentity],
6935
- factory: (sceneContext, selectionService) => {
7352
+ consumes: [SceneContextIdentity, SelectionServiceIdentity, WatcherServiceIdentity],
7353
+ factory: (sceneContext, selectionService, watcherService) => {
6936
7354
  // Ref-counted utility layers, shared across consumers.
6937
7355
  const utilityLayers = new WeakMap();
6938
7356
  const getUtilityLayer = (scene, layer = "default") => {
@@ -7031,13 +7449,11 @@ const GizmoServiceDefinition = {
7031
7449
  currentKeepDepthUtilityLayerRef = null;
7032
7450
  };
7033
7451
  gm.coordinatesMode = coordinatesModeState;
7034
- coordinatesModeInterceptToken = InterceptProperty(gm, "coordinatesMode", {
7035
- afterSet: (value) => {
7036
- if (value !== coordinatesModeState) {
7037
- coordinatesModeState = value;
7038
- coordinatesModeObservable.notifyObservers();
7039
- }
7040
- },
7452
+ coordinatesModeInterceptToken = watcherService.watchProperty(gm, "coordinatesMode", (value) => {
7453
+ if (value !== coordinatesModeState) {
7454
+ coordinatesModeState = value;
7455
+ coordinatesModeObservable.notifyObservers();
7456
+ }
7041
7457
  });
7042
7458
  currentGizmoManager = gm;
7043
7459
  }
@@ -7210,7 +7626,7 @@ const DefaultInspectorExtensionFeed = new BuiltInsExtensionFeed("Inspector", [
7210
7626
  keywords: ["creation", "tools"],
7211
7627
  ...BabylonWebResources,
7212
7628
  author: { name: "Babylon.js", forumUserName: "" },
7213
- getExtensionModuleAsync: async () => await import('./quickCreateToolsService-C9jZpIAl.js'),
7629
+ getExtensionModuleAsync: async () => await import('./quickCreateToolsService-DHfs_EZ6.js'),
7214
7630
  },
7215
7631
  {
7216
7632
  name: "Reflector",
@@ -7218,116 +7634,10 @@ const DefaultInspectorExtensionFeed = new BuiltInsExtensionFeed("Inspector", [
7218
7634
  keywords: ["reflector", "bridge", "sync", "sandbox", "tools"],
7219
7635
  ...BabylonWebResources,
7220
7636
  author: { name: "Babylon.js", forumUserName: "" },
7221
- getExtensionModuleAsync: async () => await import('./reflectorService-DGy36OAK.js'),
7637
+ getExtensionModuleAsync: async () => await import('./reflectorService-DEPuGTAZ.js'),
7222
7638
  },
7223
7639
  ]);
7224
7640
 
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
7641
  /**
7332
7642
  * Reusable component which renders a color property line containing a label, colorPicker popout, and expandable RGBA values
7333
7643
  * The expandable RGBA values are synced sliders that allow the user to modify the color's RGBA values directly
@@ -7362,30 +7672,6 @@ const ColorSliders = ({ color, onSliderChange }) => (jsxs(Fragment, { children:
7362
7672
  const Color3PropertyLine = ColorPropertyLine;
7363
7673
  const Color4PropertyLine = ColorPropertyLine;
7364
7674
 
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
7675
  /**
7390
7676
  * Wraps a text input in a property line
7391
7677
  * @param props - PropertyLineProps and InputProps
@@ -7430,21 +7716,21 @@ const TensorPropertyLine = (props) => {
7430
7716
  useEffect(() => {
7431
7717
  setVector(props.value);
7432
7718
  }, [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)}` : ""}]` }) }));
7719
+ 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
7720
  };
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 })] }));
7721
+ 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
7722
  const ToDegreesConverter = { from: Tools.ToDegrees, to: Tools.ToRadians };
7437
7723
  const RotationVectorPropertyLine = (props) => {
7438
7724
  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 }));
7725
+ const step = props.useDegrees ? 1 : 0.01;
7726
+ const precision = props.useDegrees ? 1 : 2;
7727
+ return (jsx(Vector3PropertyLine, { ...props, unit: props.useDegrees ? "°" : "rad", valueConverter: props.useDegrees ? ToDegreesConverter : undefined, step: step, precision: precision }));
7442
7728
  };
7443
7729
  const QuaternionPropertyLineInternal = TensorPropertyLine;
7444
7730
  const QuaternionPropertyLine = (props) => {
7445
7731
  QuaternionPropertyLine.displayName = "QuaternionPropertyLine";
7446
- const min = props.useDegrees ? 0 : undefined;
7447
- const max = props.useDegrees ? 360 : undefined;
7732
+ const step = props.useDegrees ? 1 : 0.01;
7733
+ const precision = props.useDegrees ? 1 : 2;
7448
7734
  const [quat, setQuat] = useState(props.value);
7449
7735
  useEffect(() => {
7450
7736
  setQuat(props.value);
@@ -7459,7 +7745,7 @@ const QuaternionPropertyLine = (props) => {
7459
7745
  const quat = Quaternion.FromEulerAngles(val.x, val.y, val.z);
7460
7746
  onQuatChange(quat);
7461
7747
  };
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" }));
7748
+ 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
7749
  };
7464
7750
  const Vector2PropertyLine = TensorPropertyLine;
7465
7751
  const Vector3PropertyLine = TensorPropertyLine;
@@ -7974,7 +8260,7 @@ const ThemeSelectorServiceDefinition = {
7974
8260
  key: "ThemeSelector",
7975
8261
  horizontalLocation: "right",
7976
8262
  verticalLocation: "top",
7977
- suppressTeachingMoment: true,
8263
+ teachingMoment: false,
7978
8264
  order: -300,
7979
8265
  component: () => {
7980
8266
  const classes = useStyles$E();
@@ -8039,7 +8325,7 @@ function MakeModularTool(options) {
8039
8325
  const [requiredExtensions, setRequiredExtensions] = useState();
8040
8326
  const [requiredExtensionsDeferred, setRequiredExtensionsDeferred] = useState();
8041
8327
  const [extensionInstallError, setExtensionInstallError] = useState();
8042
- const [rootComponent, setRootComponent] = useState();
8328
+ const [bootstrapServices, setBootstrapServices] = useState();
8043
8329
  // This is the main async initialization.
8044
8330
  useEffect(() => {
8045
8331
  const initializeExtensionManagerAsync = async () => {
@@ -8050,17 +8336,22 @@ function MakeModularTool(options) {
8050
8336
  produces: [SettingsStoreIdentity],
8051
8337
  factory: () => settingsStore,
8052
8338
  });
8339
+ // Register watcher service early since many other services will rely on it.
8340
+ // TODO: Really this should be in the Inspector layer, but we would need a way
8341
+ // to setup the WatcherContext.Provider before the root component is rendered
8342
+ // for that to work, since components will use the WatcherContext.
8343
+ await serviceContainer.addServiceAsync(WatcherServiceDefinition);
8053
8344
  // Register the shell service (top level toolbar/side pane UI layout).
8054
8345
  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.
8346
+ // Register a service that simply consumes the services we need before first render.
8056
8347
  await serviceContainer.addServiceAsync({
8057
- friendlyName: "Root Component Bootstrapper",
8058
- consumes: [RootComponentServiceIdentity],
8059
- factory: (rootComponentService) => {
8348
+ friendlyName: "Service Bootstrapper",
8349
+ consumes: [RootComponentServiceIdentity, WatcherServiceIdentity],
8350
+ factory: (rootComponentService, watcherService) => {
8060
8351
  // Use function syntax for the state setter since the root component may be a function component.
8061
- setRootComponent(() => rootComponentService.rootComponent);
8352
+ setBootstrapServices({ rootComponentService, watcherService });
8062
8353
  return {
8063
- dispose: () => setRootComponent(undefined),
8354
+ dispose: () => setBootstrapServices(undefined),
8064
8355
  };
8065
8356
  },
8066
8357
  });
@@ -8072,7 +8363,7 @@ function MakeModularTool(options) {
8072
8363
  }
8073
8364
  // Register the extension list service (for browsing/installing extensions) if extension feeds are provided.
8074
8365
  if (extensionFeeds.length > 0) {
8075
- const { ExtensionListServiceDefinition } = await import('./extensionsListService-qU9tqfBg.js');
8366
+ const { ExtensionListServiceDefinition } = await import('./extensionsListService-Duej2zkq.js');
8076
8367
  await serviceContainer.addServiceAsync(ExtensionListServiceDefinition);
8077
8368
  }
8078
8369
  // Register all external services (that make up a unique tool).
@@ -8139,12 +8430,17 @@ function MakeModularTool(options) {
8139
8430
  setExtensionInstallError(undefined);
8140
8431
  }, [setExtensionInstallError]);
8141
8432
  // 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, {}) })] }) }) }) }));
8433
+ if (!bootstrapServices) {
8434
+ return (jsx(SettingsStoreContext.Provider, { value: settingsStore, children: jsx(Theme, { className: classes.app, children: jsx(Spinner, { className: classes.spinner }) }) }));
8435
+ }
8436
+ else {
8437
+ // eslint-disable-next-line @typescript-eslint/naming-convention
8438
+ const Content = bootstrapServices.rootComponentService.rootComponent;
8439
+ return (
8440
+ // Expose the settings store as a React context so that UI components can read/write
8441
+ // settings without the ISettingsService needing to be explicitly passed around.
8442
+ 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, {}) })] }) }) }) }) }));
8443
+ }
8148
8444
  };
8149
8445
  // Set the container element to be a flex container so that the tool can be displayed properly.
8150
8446
  const originalContainerElementDisplay = containerElement.style.display;
@@ -8223,12 +8519,103 @@ const GizmoToolbarServiceDefinition = {
8223
8519
  key: "Gizmo Toolbar",
8224
8520
  verticalLocation: "top",
8225
8521
  horizontalLocation: "left",
8226
- suppressTeachingMoment: true,
8522
+ teachingMoment: false,
8227
8523
  component: () => jsx(GizmoToolbar, { gizmoService: gizmoService, sceneContext: sceneContext }),
8228
8524
  });
8229
8525
  },
8230
8526
  };
8231
8527
 
8528
+ const HighlightSelectedEntitySettingDescriptor = {
8529
+ key: "HighlightSelectedEntity",
8530
+ defaultValue: true,
8531
+ };
8532
+ const HighlightServiceDefinition = {
8533
+ friendlyName: "Highlight Service",
8534
+ consumes: [SelectionServiceIdentity, SceneContextIdentity, SettingsStoreIdentity, ThemeServiceIdentity, GizmoServiceIdentity],
8535
+ factory: (selectionService, sceneContext, settingsStore, themeService, gizmoService) => {
8536
+ let outlineLayer = null;
8537
+ let utilityLayer = null;
8538
+ let currentScene = null;
8539
+ let activeCameraObserver = null;
8540
+ function disposeOutlineLayer() {
8541
+ outlineLayer?.dispose();
8542
+ outlineLayer = null;
8543
+ utilityLayer?.dispose();
8544
+ utilityLayer = null;
8545
+ currentScene = null;
8546
+ }
8547
+ function getOrCreateOutlineLayer(scene) {
8548
+ if (!outlineLayer || currentScene !== scene) {
8549
+ disposeOutlineLayer();
8550
+ utilityLayer = gizmoService.getUtilityLayer(scene);
8551
+ outlineLayer = new SelectionOutlineLayer("InspectorSelectionOutline", utilityLayer.value.utilityLayerScene);
8552
+ updateColor(outlineLayer);
8553
+ currentScene = scene;
8554
+ }
8555
+ return outlineLayer;
8556
+ }
8557
+ function updateColor(outlineLayer) {
8558
+ outlineLayer.outlineColor = Color3.FromHexString(themeService.theme.colorBrandForeground1);
8559
+ }
8560
+ function updateHighlight() {
8561
+ const scene = sceneContext.currentScene;
8562
+ const entity = selectionService.selectedEntity instanceof AbstractMesh && !(selectionService.selectedEntity instanceof GaussianSplattingMesh)
8563
+ ? selectionService.selectedEntity
8564
+ : null;
8565
+ if (!entity || !settingsStore.readSetting(HighlightSelectedEntitySettingDescriptor) || !scene || !scene.activeCamera) {
8566
+ disposeOutlineLayer();
8567
+ return;
8568
+ }
8569
+ const layer = getOrCreateOutlineLayer(scene);
8570
+ layer.clearSelection();
8571
+ layer.addSelection(entity);
8572
+ }
8573
+ function watchActiveCamera(scene) {
8574
+ activeCameraObserver?.remove();
8575
+ activeCameraObserver = null;
8576
+ if (scene) {
8577
+ activeCameraObserver = scene.onActiveCameraChanged.add(updateHighlight);
8578
+ }
8579
+ }
8580
+ // React to theme changes.
8581
+ const themeObserver = themeService.onChanged.add(() => {
8582
+ if (outlineLayer) {
8583
+ updateColor(outlineLayer);
8584
+ }
8585
+ });
8586
+ // React to selection changes.
8587
+ const selectionObserver = selectionService.onSelectedEntityChanged.add(updateHighlight);
8588
+ // React to scene changes.
8589
+ const sceneObserver = sceneContext.currentSceneObservable.add(() => {
8590
+ // Dispose the old layer when the scene changes.
8591
+ disposeOutlineLayer();
8592
+ watchActiveCamera(sceneContext.currentScene);
8593
+ updateHighlight();
8594
+ });
8595
+ // React to setting changes.
8596
+ const settingObserver = settingsStore.onChanged.add((setting) => {
8597
+ if (setting === HighlightSelectedEntitySettingDescriptor.key) {
8598
+ updateHighlight();
8599
+ }
8600
+ });
8601
+ // Watch active camera on the initial scene.
8602
+ watchActiveCamera(sceneContext.currentScene);
8603
+ // Initial update.
8604
+ updateHighlight();
8605
+ return {
8606
+ dispose: () => {
8607
+ themeObserver.remove();
8608
+ selectionObserver.remove();
8609
+ sceneObserver.remove();
8610
+ settingObserver.remove();
8611
+ activeCameraObserver?.remove();
8612
+ activeCameraObserver = null;
8613
+ disposeOutlineLayer();
8614
+ },
8615
+ };
8616
+ },
8617
+ };
8618
+
8232
8619
  const useStyles$B = makeStyles({
8233
8620
  badge: {
8234
8621
  margin: tokens.spacingHorizontalXXS,
@@ -8243,7 +8630,8 @@ const MiniStatsServiceDefinition = {
8243
8630
  key: "Mini Stats",
8244
8631
  verticalLocation: "bottom",
8245
8632
  horizontalLocation: "right",
8246
- suppressTeachingMoment: true,
8633
+ order: 300 /* DefaultToolbarItemOrder.FrameRate */,
8634
+ teachingMoment: false,
8247
8635
  component: () => {
8248
8636
  const classes = useStyles$B();
8249
8637
  const scene = useObservableState(useCallback(() => sceneContext.currentScene, [sceneContext.currentScene]), sceneContext.currentSceneObservable);
@@ -12429,7 +12817,7 @@ const SoundCommandProperties = (props) => {
12429
12817
  else {
12430
12818
  sound.play();
12431
12819
  }
12432
- } }), jsx(Property, { component: SyncedSliderPropertyLine, label: "Volume", functionPath: "setVolume", value: volume, min: 0, max: 5, step: 0.1, onChange: (value) => {
12820
+ } }), jsx(Property, { component: NumberInputPropertyLine, label: "Volume", functionPath: "setVolume", value: volume, min: 0, step: 0.1, onChange: (value) => {
12433
12821
  sound.setVolume(value);
12434
12822
  } }), jsx(BoundProperty, { component: SwitchPropertyLine, label: "Loop", target: sound, propertyKey: "loop" })] }));
12435
12823
  };
@@ -12469,7 +12857,7 @@ const ArcRotateCameraTransformProperties = (props) => {
12469
12857
  const upperBetaLimit = useProperty(camera, "upperBetaLimit") ?? Math.PI;
12470
12858
  const lowerRadiusLimit = useProperty(camera, "lowerRadiusLimit");
12471
12859
  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 }))] }));
12860
+ 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
12861
  };
12474
12862
  const ArcRotateCameraControlProperties = (props) => {
12475
12863
  const { camera } = props;
@@ -12481,8 +12869,19 @@ const ArcRotateCameraCollisionProperties = (props) => {
12481
12869
  };
12482
12870
  const ArcRotateCameraLimitsProperties = (props) => {
12483
12871
  const { camera } = props;
12872
+ const [toDisplayAngle, fromDisplayAngle, useDegrees] = useAngleConverters();
12873
+ const minAlphaLimit = 0;
12874
+ const maxAlphaLimit = Math.PI * 2;
12875
+ const minBetaLimit = -Math.PI;
12876
+ const maxBetaLimit = Math.PI;
12877
+ const lowerAlphaLimit = useProperty(camera, "lowerAlphaLimit") ?? minAlphaLimit;
12878
+ const upperAlphaLimit = useProperty(camera, "upperAlphaLimit") ?? maxAlphaLimit;
12879
+ const lowerBetaLimit = useProperty(camera, "lowerBetaLimit") ?? minBetaLimit;
12880
+ const upperBetaLimit = useProperty(camera, "upperBetaLimit") ?? maxBetaLimit;
12881
+ const lowerRadiusLimit = useProperty(camera, "lowerRadiusLimit");
12882
+ const upperRadiusLimit = useProperty(camera, "upperRadiusLimit");
12484
12883
  // 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" })] }));
12884
+ 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
12885
  };
12487
12886
  const ArcRotateCameraBehaviorsProperties = (props) => {
12488
12887
  const { camera } = props;
@@ -12499,7 +12898,7 @@ const GeospatialCameraTransformProperties = (props) => {
12499
12898
  const pitchMax = limits?.pitchMax ?? Math.PI / 2;
12500
12899
  const radiusMin = limits?.radiusMin ?? 0;
12501
12900
  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" })] }));
12901
+ 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
12902
  };
12504
12903
  const GeospatialCameraCollisionProperties = (props) => {
12505
12904
  const { camera } = props;
@@ -12576,7 +12975,7 @@ const CameraGeneralProperties = (props) => {
12576
12975
  const { camera } = props;
12577
12976
  const [toDisplayAngle, fromDisplayAngle, useDegrees] = useAngleConverters();
12578
12977
  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 })] }) })] }));
12978
+ 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
12979
  };
12581
12980
 
12582
12981
  const FollowCameraTransformProperties = (props) => {
@@ -13295,7 +13694,7 @@ const Gradient = (props) => {
13295
13694
  // Only use compact mode when there are numeric values (spinbuttons) taking up space
13296
13695
  const hasNumericValues = !(gradient.value1 instanceof Color3 || gradient.value1 instanceof Color4) ||
13297
13696
  (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 }) })] }));
13697
+ 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
13698
  };
13300
13699
  const FactorGradientCast = Gradient;
13301
13700
  const Color3GradientCast = Gradient;
@@ -14166,7 +14565,7 @@ const PBRBaseMaterialDebugProperties = (props) => {
14166
14565
  const SkyMaterialProperties = (props) => {
14167
14566
  const { material } = props;
14168
14567
  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" })] }));
14568
+ 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
14569
  };
14171
14570
 
14172
14571
  const StandardMaterialGeneralProperties = (props) => {
@@ -17336,7 +17735,7 @@ const SpriteGeneralProperties = (props) => {
17336
17735
  const SpriteTransformProperties = (props) => {
17337
17736
  const { sprite } = props;
17338
17737
  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")] }));
17738
+ 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
17739
  };
17341
17740
  const SpriteAnimationProperties = (props) => {
17342
17741
  const { sprite } = props;
@@ -18930,7 +19329,7 @@ const Contrast = {
18930
19329
  const handleExposureChange = (_, data) => {
18931
19330
  setExposure(data.value);
18932
19331
  };
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 })] })] }));
19332
+ 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
19333
  },
18935
19334
  };
18936
19335
  },
@@ -19152,7 +19551,7 @@ const Paintbrush = {
19152
19551
  const handleWidthChange = (_, data) => {
19153
19552
  setWidth(data.value);
19154
19553
  };
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 })] }) }));
19554
+ 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
19555
  },
19157
19556
  };
19158
19557
  },
@@ -19397,7 +19796,7 @@ const TransformProperties = (props) => {
19397
19796
  const quatRotation = useQuaternionProperty(transform, "rotationQuaternion");
19398
19797
  const [useDegrees] = useSetting(UseDegreesSettingDescriptor);
19399
19798
  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" })] }));
19799
+ 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
19800
  };
19402
19801
 
19403
19802
  const TransformPropertiesServiceDefinition = {
@@ -19425,8 +19824,8 @@ const TransformPropertiesServiceDefinition = {
19425
19824
 
19426
19825
  const AnimationGroupExplorerServiceDefinition = {
19427
19826
  friendlyName: "Animation Group Explorer",
19428
- consumes: [SceneExplorerServiceIdentity, SceneContextIdentity],
19429
- factory: (sceneExplorerService, sceneContext) => {
19827
+ consumes: [SceneExplorerServiceIdentity, SceneContextIdentity, WatcherServiceIdentity],
19828
+ factory: (sceneExplorerService, sceneContext, watcherService) => {
19430
19829
  const scene = sceneContext.currentScene;
19431
19830
  if (!scene) {
19432
19831
  return undefined;
@@ -19439,11 +19838,7 @@ const AnimationGroupExplorerServiceDefinition = {
19439
19838
  getEntityDisplayInfo: (entity) => {
19440
19839
  const namedEntity = entity instanceof AnimationGroup ? entity : entity.animation;
19441
19840
  const onChangeObservable = new Observable();
19442
- const nameHookToken = InterceptProperty(namedEntity, "name", {
19443
- afterSet: () => {
19444
- onChangeObservable.notifyObservers();
19445
- },
19446
- });
19841
+ const nameHookToken = watcherService.watchProperty(namedEntity, "name", () => onChangeObservable.notifyObservers());
19447
19842
  return {
19448
19843
  get name() {
19449
19844
  return namedEntity.name;
@@ -19509,8 +19904,8 @@ const AnimationGroupExplorerServiceDefinition = {
19509
19904
 
19510
19905
  const AtmosphereExplorerServiceDefinition = {
19511
19906
  friendlyName: "Atmosphere Explorer",
19512
- consumes: [SceneExplorerServiceIdentity, SceneContextIdentity],
19513
- factory: (sceneExplorerService, sceneContext) => {
19907
+ consumes: [SceneExplorerServiceIdentity, SceneContextIdentity, WatcherServiceIdentity],
19908
+ factory: (sceneExplorerService, sceneContext, watcherService) => {
19514
19909
  const scene = sceneContext.currentScene;
19515
19910
  if (!scene) {
19516
19911
  return undefined;
@@ -19521,11 +19916,7 @@ const AtmosphereExplorerServiceDefinition = {
19521
19916
  getRootEntities: () => (scene.getExternalData("atmosphere") ? [scene.getExternalData("atmosphere")] : []),
19522
19917
  getEntityDisplayInfo: (atmosphere) => {
19523
19918
  const onChangeObservable = new Observable();
19524
- const nameHookToken = InterceptProperty(atmosphere, "name", {
19525
- afterSet: () => {
19526
- onChangeObservable.notifyObservers();
19527
- },
19528
- });
19919
+ const nameHookToken = watcherService.watchProperty(atmosphere, "name", () => onChangeObservable.notifyObservers());
19529
19920
  return {
19530
19921
  get name() {
19531
19922
  return atmosphere.name;
@@ -19584,8 +19975,8 @@ const DisposableCommandServiceDefinition = {
19584
19975
 
19585
19976
  const EffectLayerExplorerServiceDefinition = {
19586
19977
  friendlyName: "Effect Layer Explorer",
19587
- consumes: [SceneExplorerServiceIdentity, SceneContextIdentity],
19588
- factory: (sceneExplorerService, sceneContext) => {
19978
+ consumes: [SceneExplorerServiceIdentity, SceneContextIdentity, WatcherServiceIdentity],
19979
+ factory: (sceneExplorerService, sceneContext, watcherService) => {
19589
19980
  const scene = sceneContext.currentScene;
19590
19981
  if (!scene) {
19591
19982
  return undefined;
@@ -19596,11 +19987,7 @@ const EffectLayerExplorerServiceDefinition = {
19596
19987
  getRootEntities: () => scene.effectLayers,
19597
19988
  getEntityDisplayInfo: (effectLayer) => {
19598
19989
  const onChangeObservable = new Observable();
19599
- const nameHookToken = InterceptProperty(effectLayer, "name", {
19600
- afterSet: () => {
19601
- onChangeObservable.notifyObservers();
19602
- },
19603
- });
19990
+ const nameHookToken = watcherService.watchProperty(effectLayer, "name", () => onChangeObservable.notifyObservers());
19604
19991
  return {
19605
19992
  get name() {
19606
19993
  return effectLayer.name;
@@ -19626,8 +20013,8 @@ const EffectLayerExplorerServiceDefinition = {
19626
20013
 
19627
20014
  const FrameGraphExplorerServiceDefinition = {
19628
20015
  friendlyName: "Frame Graph Explorer",
19629
- consumes: [SceneExplorerServiceIdentity, SceneContextIdentity],
19630
- factory: (sceneExplorerService, sceneContext) => {
20016
+ consumes: [SceneExplorerServiceIdentity, SceneContextIdentity, WatcherServiceIdentity],
20017
+ factory: (sceneExplorerService, sceneContext, watcherService) => {
19631
20018
  const scene = sceneContext.currentScene;
19632
20019
  if (!scene) {
19633
20020
  return undefined;
@@ -19638,11 +20025,7 @@ const FrameGraphExplorerServiceDefinition = {
19638
20025
  getRootEntities: () => scene.frameGraphs,
19639
20026
  getEntityDisplayInfo: (frameGraph) => {
19640
20027
  const onChangeObservable = new Observable();
19641
- const nameHookToken = InterceptProperty(frameGraph, "name", {
19642
- afterSet: () => {
19643
- onChangeObservable.notifyObservers();
19644
- },
19645
- });
20028
+ const nameHookToken = watcherService.watchProperty(frameGraph, "name", () => onChangeObservable.notifyObservers());
19646
20029
  return {
19647
20030
  get name() {
19648
20031
  return frameGraph.name;
@@ -19663,9 +20046,7 @@ const FrameGraphExplorerServiceDefinition = {
19663
20046
  order: 900 /* DefaultCommandsOrder.FrameGraphPlay */,
19664
20047
  getCommand: (frameGraph) => {
19665
20048
  const onChangeObservable = new Observable();
19666
- const frameGraphHook = InterceptProperty(scene, "frameGraph", {
19667
- afterSet: () => onChangeObservable.notifyObservers(),
19668
- });
20049
+ const frameGraphHook = watcherService.watchProperty(scene, "frameGraph", () => onChangeObservable.notifyObservers());
19669
20050
  return {
19670
20051
  type: "toggle",
19671
20052
  displayName: "Make Active",
@@ -19728,8 +20109,8 @@ function IsControl(entity) {
19728
20109
  }
19729
20110
  const GuiExplorerServiceDefinition = {
19730
20111
  friendlyName: "GUI Explorer",
19731
- consumes: [SceneExplorerServiceIdentity, SceneContextIdentity],
19732
- factory: (sceneExplorerService, sceneContext) => {
20112
+ consumes: [SceneExplorerServiceIdentity, SceneContextIdentity, WatcherServiceIdentity],
20113
+ factory: (sceneExplorerService, sceneContext, watcherService) => {
19733
20114
  const scene = sceneContext.currentScene;
19734
20115
  if (!scene) {
19735
20116
  return undefined;
@@ -19755,10 +20136,8 @@ const GuiExplorerServiceDefinition = {
19755
20136
  const disposeActions = [];
19756
20137
  const onChangeObservable = new Observable();
19757
20138
  disposeActions.push(() => onChangeObservable.clear());
19758
- const nameHookToken = InterceptProperty(entity, "name", {
19759
- afterSet: () => {
19760
- onChangeObservable.notifyObservers();
19761
- },
20139
+ const nameHookToken = watcherService.watchProperty(entity, "name", () => {
20140
+ onChangeObservable.notifyObservers();
19762
20141
  });
19763
20142
  disposeActions.push(() => nameHookToken.dispose());
19764
20143
  if (!IsAdvancedDynamicTexture(entity) && IsContainer(entity)) {
@@ -19815,9 +20194,7 @@ const GuiExplorerServiceDefinition = {
19815
20194
  order: 1000 /* DefaultCommandsOrder.GuiHighlight */,
19816
20195
  getCommand: (control) => {
19817
20196
  const onChangeObservable = new Observable();
19818
- const showBoundingBoxHook = InterceptProperty(control, "isHighlighted", {
19819
- afterSet: () => onChangeObservable.notifyObservers(),
19820
- });
20197
+ const showBoundingBoxHook = watcherService.watchProperty(control, "isHighlighted", () => onChangeObservable.notifyObservers());
19821
20198
  return {
19822
20199
  type: "toggle",
19823
20200
  get displayName() {
@@ -19873,8 +20250,8 @@ const GuiExplorerServiceDefinition = {
19873
20250
 
19874
20251
  const MaterialExplorerServiceDefinition = {
19875
20252
  friendlyName: "Material Explorer",
19876
- consumes: [SceneExplorerServiceIdentity, SceneContextIdentity],
19877
- factory: (sceneExplorerService, sceneContext) => {
20253
+ consumes: [SceneExplorerServiceIdentity, SceneContextIdentity, WatcherServiceIdentity],
20254
+ factory: (sceneExplorerService, sceneContext, watcherService) => {
19878
20255
  const scene = sceneContext.currentScene;
19879
20256
  if (!scene) {
19880
20257
  return undefined;
@@ -19885,11 +20262,7 @@ const MaterialExplorerServiceDefinition = {
19885
20262
  getRootEntities: () => [...scene.materials, ...scene.multiMaterials],
19886
20263
  getEntityDisplayInfo: (material) => {
19887
20264
  const onChangeObservable = new Observable();
19888
- const nameHookToken = InterceptProperty(material, "name", {
19889
- afterSet: () => {
19890
- onChangeObservable.notifyObservers();
19891
- },
19892
- });
20265
+ const nameHookToken = watcherService.watchProperty(material, "name", () => onChangeObservable.notifyObservers());
19893
20266
  return {
19894
20267
  get name() {
19895
20268
  return material.name;
@@ -19929,8 +20302,8 @@ const MaterialExplorerServiceDefinition = {
19929
20302
 
19930
20303
  const NodeExplorerServiceDefinition = {
19931
20304
  friendlyName: "Node Explorer",
19932
- consumes: [SceneExplorerServiceIdentity, SceneContextIdentity, GizmoServiceIdentity],
19933
- factory: (sceneExplorerService, sceneContext, gizmoService) => {
20305
+ consumes: [SceneExplorerServiceIdentity, SceneContextIdentity, GizmoServiceIdentity, WatcherServiceIdentity],
20306
+ factory: (sceneExplorerService, sceneContext, gizmoService, watcherService) => {
19934
20307
  const scene = sceneContext.currentScene;
19935
20308
  if (!scene) {
19936
20309
  return undefined;
@@ -19969,14 +20342,8 @@ const NodeExplorerServiceDefinition = {
19969
20342
  getEntityChildren: (node) => node.getChildren(),
19970
20343
  getEntityDisplayInfo: (node) => {
19971
20344
  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
- });
20345
+ const nameHookToken = watcherService.watchProperty(node, "name", () => onChangeObservable.notifyObservers());
20346
+ const parentHookToken = watcherService.watchProperty(node, "parent", () => nodeMovedObservable.notifyObservers(node));
19980
20347
  return {
19981
20348
  get name() {
19982
20349
  return node.name;
@@ -20039,9 +20406,7 @@ const NodeExplorerServiceDefinition = {
20039
20406
  order: 1000 /* DefaultCommandsOrder.MeshBoundingBox */,
20040
20407
  getCommand: (mesh) => {
20041
20408
  const onChangeObservable = new Observable();
20042
- const showBoundingBoxHook = InterceptProperty(mesh, "showBoundingBox", {
20043
- afterSet: () => onChangeObservable.notifyObservers(),
20044
- });
20409
+ const showBoundingBoxHook = watcherService.watchProperty(mesh, "showBoundingBox", () => onChangeObservable.notifyObservers());
20045
20410
  return {
20046
20411
  type: "toggle",
20047
20412
  get displayName() {
@@ -20067,9 +20432,7 @@ const NodeExplorerServiceDefinition = {
20067
20432
  order: 1100 /* DefaultCommandsOrder.MeshVisibility */,
20068
20433
  getCommand: (mesh) => {
20069
20434
  const onChangeObservable = new Observable();
20070
- const isVisibleHook = InterceptProperty(mesh, "isVisible", {
20071
- afterSet: () => onChangeObservable.notifyObservers(),
20072
- });
20435
+ const isVisibleHook = watcherService.watchProperty(mesh, "isVisible", () => onChangeObservable.notifyObservers());
20073
20436
  return {
20074
20437
  type: "toggle",
20075
20438
  get displayName() {
@@ -20221,8 +20584,8 @@ const NodeExplorerServiceDefinition = {
20221
20584
 
20222
20585
  const ParticleSystemExplorerServiceDefinition = {
20223
20586
  friendlyName: "Particle System Explorer",
20224
- consumes: [SceneExplorerServiceIdentity, SceneContextIdentity],
20225
- factory: (sceneExplorerService, sceneContext) => {
20587
+ consumes: [SceneExplorerServiceIdentity, SceneContextIdentity, WatcherServiceIdentity],
20588
+ factory: (sceneExplorerService, sceneContext, watcherService) => {
20226
20589
  const scene = sceneContext.currentScene;
20227
20590
  if (!scene) {
20228
20591
  return undefined;
@@ -20233,11 +20596,7 @@ const ParticleSystemExplorerServiceDefinition = {
20233
20596
  getRootEntities: () => scene.particleSystems,
20234
20597
  getEntityDisplayInfo: (particleSystem) => {
20235
20598
  const onChangeObservable = new Observable();
20236
- const nameHookToken = InterceptProperty(particleSystem, "name", {
20237
- afterSet: () => {
20238
- onChangeObservable.notifyObservers();
20239
- },
20240
- });
20599
+ const nameHookToken = watcherService.watchProperty(particleSystem, "name", () => onChangeObservable.notifyObservers());
20241
20600
  return {
20242
20601
  get name() {
20243
20602
  return particleSystem.name;
@@ -20277,8 +20636,8 @@ const ParticleSystemExplorerServiceDefinition = {
20277
20636
 
20278
20637
  const PostProcessExplorerServiceDefinition = {
20279
20638
  friendlyName: "Post Process Explorer",
20280
- consumes: [SceneExplorerServiceIdentity, SceneContextIdentity],
20281
- factory: (sceneExplorerService, sceneContext) => {
20639
+ consumes: [SceneExplorerServiceIdentity, SceneContextIdentity, WatcherServiceIdentity],
20640
+ factory: (sceneExplorerService, sceneContext, watcherService) => {
20282
20641
  const scene = sceneContext.currentScene;
20283
20642
  if (!scene) {
20284
20643
  return undefined;
@@ -20289,11 +20648,7 @@ const PostProcessExplorerServiceDefinition = {
20289
20648
  getRootEntities: () => scene.postProcesses,
20290
20649
  getEntityDisplayInfo: (postProcess) => {
20291
20650
  const onChangeObservable = new Observable();
20292
- const nameHookToken = InterceptProperty(postProcess, "name", {
20293
- afterSet: () => {
20294
- onChangeObservable.notifyObservers();
20295
- },
20296
- });
20651
+ const nameHookToken = watcherService.watchProperty(postProcess, "name", () => onChangeObservable.notifyObservers());
20297
20652
  return {
20298
20653
  get name() {
20299
20654
  return postProcess.name;
@@ -20351,8 +20706,8 @@ const RenderingPipelineExplorerServiceDefinition = {
20351
20706
 
20352
20707
  const SkeletonExplorerServiceDefinition = {
20353
20708
  friendlyName: "Skeleton Explorer",
20354
- consumes: [SceneExplorerServiceIdentity, SceneContextIdentity],
20355
- factory: (sceneExplorerService, sceneContext) => {
20709
+ consumes: [SceneExplorerServiceIdentity, SceneContextIdentity, WatcherServiceIdentity],
20710
+ factory: (sceneExplorerService, sceneContext, watcherService) => {
20356
20711
  const scene = sceneContext.currentScene;
20357
20712
  if (!scene) {
20358
20713
  return undefined;
@@ -20365,16 +20720,8 @@ const SkeletonExplorerServiceDefinition = {
20365
20720
  getEntityChildren: (skeletonOrBone) => skeletonOrBone.getChildren(),
20366
20721
  getEntityDisplayInfo: (skeletonOrBone) => {
20367
20722
  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
- });
20723
+ const nameHookToken = watcherService.watchProperty(skeletonOrBone, "name", () => onChangeObservable.notifyObservers());
20724
+ const parentHookToken = skeletonOrBone instanceof Skeleton ? null : watcherService.watchProperty(skeletonOrBone, "parent", () => boneMovedObservable.notifyObservers(skeletonOrBone));
20378
20725
  return {
20379
20726
  get name() {
20380
20727
  return skeletonOrBone.name;
@@ -20402,8 +20749,8 @@ const SkeletonExplorerServiceDefinition = {
20402
20749
 
20403
20750
  const SoundExplorerServiceDefinition = {
20404
20751
  friendlyName: "Sound Explorer",
20405
- consumes: [SceneExplorerServiceIdentity, SceneContextIdentity],
20406
- factory: (sceneExplorerService, sceneContext) => {
20752
+ consumes: [SceneExplorerServiceIdentity, SceneContextIdentity, WatcherServiceIdentity],
20753
+ factory: (sceneExplorerService, sceneContext, watcherService) => {
20407
20754
  const scene = sceneContext.currentScene;
20408
20755
  if (!scene) {
20409
20756
  return undefined;
@@ -20429,25 +20776,14 @@ const SoundExplorerServiceDefinition = {
20429
20776
  // If _mainSoundTrack is already defined, set up hooks immediately.
20430
20777
  hookMainSoundTrack(scene.mainSoundTrack);
20431
20778
  // 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
- });
20779
+ const mainSoundTrackHook = watcherService.watchProperty(scene, "_mainSoundTrack", () => hookMainSoundTrack(scene._mainSoundTrack));
20435
20780
  const sectionRegistration = sceneExplorerService.addSection({
20436
20781
  displayName: "Sounds",
20437
20782
  order: 1400 /* DefaultSectionsOrder.Sounds */,
20438
20783
  getRootEntities: () => scene.mainSoundTrack?.soundCollection ?? [],
20439
20784
  getEntityDisplayInfo: (sound) => {
20440
20785
  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
- });
20786
+ const nameHookToken = watcherService.watchProperty(sound, "name", () => onChangeObservable.notifyObservers());
20451
20787
  return {
20452
20788
  get name() {
20453
20789
  return sound.name;
@@ -20455,7 +20791,6 @@ const SoundExplorerServiceDefinition = {
20455
20791
  onChange: onChangeObservable,
20456
20792
  dispose: () => {
20457
20793
  nameHookToken.dispose();
20458
- displayNameHookToken.dispose();
20459
20794
  onChangeObservable.clear();
20460
20795
  },
20461
20796
  };
@@ -20479,8 +20814,8 @@ const SoundExplorerServiceDefinition = {
20479
20814
 
20480
20815
  const SpriteManagerExplorerServiceDefinition = {
20481
20816
  friendlyName: "Sprite Manager Explorer",
20482
- consumes: [SceneExplorerServiceIdentity, SceneContextIdentity],
20483
- factory: (sceneExplorerService, sceneContext) => {
20817
+ consumes: [SceneExplorerServiceIdentity, SceneContextIdentity, WatcherServiceIdentity],
20818
+ factory: (sceneExplorerService, sceneContext, watcherService) => {
20484
20819
  const scene = sceneContext.currentScene;
20485
20820
  if (!scene) {
20486
20821
  return undefined;
@@ -20492,9 +20827,7 @@ const SpriteManagerExplorerServiceDefinition = {
20492
20827
  getEntityChildren: (spriteEntity) => (spriteEntity instanceof Sprite ? [] : spriteEntity.sprites),
20493
20828
  getEntityDisplayInfo: (spriteEntity) => {
20494
20829
  const onChangeObservable = new Observable();
20495
- const nameHookToken = InterceptProperty(spriteEntity, "name", {
20496
- afterSet: () => onChangeObservable.notifyObservers(),
20497
- });
20830
+ const nameHookToken = watcherService.watchProperty(spriteEntity, "name", () => onChangeObservable.notifyObservers());
20498
20831
  return {
20499
20832
  get name() {
20500
20833
  return spriteEntity.name;
@@ -20552,8 +20885,8 @@ const SpriteManagerExplorerServiceDefinition = {
20552
20885
 
20553
20886
  const TextureExplorerServiceDefinition = {
20554
20887
  friendlyName: "Texture Explorer",
20555
- consumes: [SceneExplorerServiceIdentity, SceneContextIdentity],
20556
- factory: (sceneExplorerService, sceneContext) => {
20888
+ consumes: [SceneExplorerServiceIdentity, SceneContextIdentity, WatcherServiceIdentity],
20889
+ factory: (sceneExplorerService, sceneContext, watcherService) => {
20557
20890
  const scene = sceneContext.currentScene;
20558
20891
  if (!scene) {
20559
20892
  return undefined;
@@ -20564,16 +20897,8 @@ const TextureExplorerServiceDefinition = {
20564
20897
  getRootEntities: () => scene.textures.filter((texture) => texture.getClassName() !== "AdvancedDynamicTexture"),
20565
20898
  getEntityDisplayInfo: (texture) => {
20566
20899
  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
- });
20900
+ const displayNameHookToken = watcherService.watchProperty(texture, "displayName", () => onChangeObservable.notifyObservers());
20901
+ const nameHookToken = watcherService.watchProperty(texture, "name", () => onChangeObservable.notifyObservers());
20577
20902
  return {
20578
20903
  get name() {
20579
20904
  return texture.displayName || texture.name || `${texture.getClassName() || "Unnamed Texture"} (${texture.uniqueId})`;
@@ -21123,97 +21448,6 @@ const GLTFValidationServiceDefinition = {
21123
21448
  },
21124
21449
  };
21125
21450
 
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
21451
  const PickingToolbar = (props) => {
21218
21452
  const { scene, selectEntity, gizmoService, ignoreBackfaces, highlightSelectedEntity, onHighlightSelectedEntityChange } = props;
21219
21453
  const meshDataCache = useMemo(() => new WeakMap(), [scene]);
@@ -21322,7 +21556,7 @@ const PickingServiceDefinition = {
21322
21556
  key: "Picking Service",
21323
21557
  verticalLocation: "top",
21324
21558
  horizontalLocation: "left",
21325
- suppressTeachingMoment: true,
21559
+ teachingMoment: false,
21326
21560
  component: () => {
21327
21561
  const scene = useObservableState(() => sceneContext.currentScene, sceneContext.currentSceneObservable);
21328
21562
  const selectEntity = useCallback((entity) => (selectionService.selectedEntity = entity), []);
@@ -21378,7 +21612,11 @@ const UserFeedbackServiceDefinition = {
21378
21612
  key: "User Feedback",
21379
21613
  verticalLocation: "bottom",
21380
21614
  horizontalLocation: "right",
21381
- suppressTeachingMoment: true,
21615
+ order: 100 /* DefaultToolbarItemOrder.Feedback */,
21616
+ teachingMoment: {
21617
+ title: "Feedback",
21618
+ description: "Press this button to give feedback on Inspector v2 and help us prioritize new features and improvements!",
21619
+ },
21382
21620
  component: () => {
21383
21621
  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
21622
  },
@@ -21569,7 +21807,9 @@ function ShowInspector(scene, options = {}) {
21569
21807
  // Tools pane tab and related services.
21570
21808
  ToolsServiceDefinition, ExportServiceDefinition, GLTFAnimationImportServiceDefinition, GLTFLoaderOptionsServiceDefinition, GLTFValidationServiceDefinition, CaptureToolsDefinition,
21571
21809
  // Settings pane tab and related services.
21572
- SettingsServiceDefinition, ShellSettingsServiceDefinition,
21810
+ SettingsServiceDefinition, WatcherSettingsServiceDefinition, ShellSettingsServiceDefinition,
21811
+ // Adds a button to refresh all properties manually (when watcher is in "manual" mode).
21812
+ WatcherRefreshToolbarServiceDefinition,
21573
21813
  // 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
21814
  SelectionServiceDefinition,
21575
21815
  // Gizmos for manipulating objects in the scene.
@@ -21803,19 +22043,15 @@ function ConvertOptions(v1Options) {
21803
22043
  const { additionalNodes } = v1Options;
21804
22044
  const additionalNodesServiceDefinition = {
21805
22045
  friendlyName: "Additional Nodes (Backward Compatibility)",
21806
- consumes: [SceneExplorerServiceIdentity],
21807
- factory: (sceneExplorerService) => {
22046
+ consumes: [SceneExplorerServiceIdentity, WatcherServiceIdentity],
22047
+ factory: (sceneExplorerService, watcherService) => {
21808
22048
  const sceneExplorerSectionRegistrations = additionalNodes.map((node) => sceneExplorerService.addSection({
21809
22049
  displayName: node.name,
21810
22050
  order: Number.MAX_SAFE_INTEGER,
21811
22051
  getRootEntities: () => node.getContent(),
21812
22052
  getEntityDisplayInfo: (entity) => {
21813
22053
  const onChangeObservable = new Observable();
21814
- const nameHookToken = InterceptProperty(entity, "name", {
21815
- afterSet: () => {
21816
- onChangeObservable.notifyObservers();
21817
- },
21818
- });
22054
+ const nameHookToken = watcherService.watchProperty(entity, "name", () => onChangeObservable.notifyObservers());
21819
22055
  return {
21820
22056
  get name() {
21821
22057
  return entity.name;
@@ -22406,5 +22642,5 @@ const TextAreaPropertyLine = (props) => {
22406
22642
  // Attach Inspector v2 to Scene.debugLayer as a side effect for back compat.
22407
22643
  AttachDebugLayer();
22408
22644
 
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
22645
+ export { useObservableCollection 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, useColor4Property as W, useQuaternionProperty as X, MakePropertyHook as Y, useInterceptObservable as Z, useEventfulState as _, useProperty as a, Pane as a$, useOrderedObservableCollection as a0, usePollingObservable as a1, useResource as a2, useAsyncResource as a3, useSetting as a4, useAngleConverters as a5, MakeTeachingMoment as a6, MakeDialogTeachingMoment as a7, MakePopoverTeachingMoment 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, 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 };
22646
+ //# sourceMappingURL=index-BvYg0Psk.js.map