@babylonjs/inspector 8.51.2 → 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, RendererProvider, createDOMRenderer, 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;
@@ -1258,9 +1274,9 @@ function useAccordionSectionItemState(props) {
1258
1274
  }
1259
1275
  prevItemIdRef.current = itemId;
1260
1276
  }, [itemId, sectionCtx?.sectionId]);
1261
- // Register item and detect duplicates
1277
+ // Register item and detect duplicates (skip nested items, as children of other AccordionSectionItem should not participate in pin/hide/search).
1262
1278
  useEffect(() => {
1263
- if (!accordionCtx || !itemUniqueId) {
1279
+ if (!accordionCtx || !itemUniqueId || isNested) {
1264
1280
  return;
1265
1281
  }
1266
1282
  const { registeredItemIds } = accordionCtx;
@@ -1272,7 +1288,7 @@ function useAccordionSectionItemState(props) {
1272
1288
  return () => {
1273
1289
  registeredItemIds.delete(itemUniqueId);
1274
1290
  };
1275
- }, [accordionCtx, itemUniqueId, itemId, itemLabel, sectionCtx?.sectionId]);
1291
+ }, [accordionCtx, itemUniqueId, itemId, itemLabel, sectionCtx?.sectionId, isNested]);
1276
1292
  // If no context, static item, or nested, return undefined
1277
1293
  if (!accordionCtx || staticItem) {
1278
1294
  return undefined;
@@ -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,15 +1673,15 @@ 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
- setChecked((prev) => {
1664
- const enabled = !prev;
1679
+ setChecked((prevChecked) => {
1680
+ const enabled = !prevChecked;
1665
1681
  onChange(enabled);
1666
1682
  return enabled;
1667
1683
  });
1668
- }, [setChecked]);
1684
+ }, [onChange]);
1669
1685
  useEffect(() => {
1670
1686
  setChecked(props.value);
1671
1687
  }, [props.value]);
@@ -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
  };
@@ -2752,22 +2768,9 @@ const ChildWindow = (props) => {
2752
2768
  body.style.padding = "0";
2753
2769
  body.style.display = "flex";
2754
2770
  body.style.overflow = "hidden";
2755
- const applyWindowState = () => {
2756
- // Setup the window state, including creating a Fluent/Griffel "renderer" for managing runtime styles/classes in the child window.
2757
- setWindowState({ mountNode: body, renderer: createDOMRenderer(childWindow.document) });
2758
- onOpenChange?.(true);
2759
- };
2760
- // Once the child window document is ready, setup the window state which will trigger another effect that renders into the child window.
2761
- if (childWindow.document.readyState === "complete") {
2762
- applyWindowState();
2763
- }
2764
- else {
2765
- const onChildWindowLoad = () => {
2766
- applyWindowState();
2767
- };
2768
- childWindow.addEventListener("load", onChildWindowLoad, { once: true });
2769
- disposeActions.push(() => childWindow.removeEventListener("load", onChildWindowLoad));
2770
- }
2771
+ // Setup the window state, including creating a Fluent/Griffel "renderer" for managing runtime styles/classes in the child window.
2772
+ setWindowState({ mountNode: body, renderer: createDOMRenderer(childWindow.document) });
2773
+ onOpenChange?.(true);
2771
2774
  // When the child window is closed for any reason, transition back to a closed state.
2772
2775
  const onChildWindowUnload = () => {
2773
2776
  setWindowState(undefined);
@@ -2909,7 +2912,7 @@ const RightSidePaneHeightAdjustSettingDescriptor = {
2909
2912
  };
2910
2913
  const RootComponentServiceIdentity = Symbol("RootComponent");
2911
2914
  const ShellServiceIdentity = Symbol("ShellService");
2912
- const useStyles$M = makeStyles({
2915
+ const useStyles$N = makeStyles({
2913
2916
  mainView: {
2914
2917
  flex: 1,
2915
2918
  display: "flex",
@@ -3112,34 +3115,38 @@ const DockMenu = (props) => {
3112
3115
  };
3113
3116
  const PaneHeader = (props) => {
3114
3117
  const { id, title, dockOptions } = props;
3115
- const classes = useStyles$M();
3118
+ const classes = useStyles$N();
3116
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, {}) }) })] }));
3117
3120
  };
3118
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.
3119
- const ToolbarItem = ({ verticalLocation, horizontalLocation, id, component: Component, displayName: displayName, suppressTeachingMoment }) => {
3120
- 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();
3121
3126
  const useTeachingMoment = useMemo(() => MakePopoverTeachingMoment(`Bar/${verticalLocation}/${horizontalLocation}/${displayName ?? id}`), [displayName, id]);
3122
- const teachingMoment = useTeachingMoment(suppressTeachingMoment);
3123
- 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, {}) })] }));
3124
3131
  };
3125
3132
  // TODO: Handle overflow, possibly via https://react.fluentui.dev/?path=/docs/components-overflow--docs with priority.
3126
3133
  // This component just renders a toolbar with left aligned toolbar items on the left and right aligned toolbar items on the right.
3127
3134
  const Toolbar = ({ location, components }) => {
3128
- const classes = useStyles$M();
3135
+ const classes = useStyles$N();
3129
3136
  const leftComponents = useMemo(() => components.filter((entry) => entry.horizontalLocation === "left"), [components]);
3130
3137
  const rightComponents = useMemo(() => components.filter((entry) => entry.horizontalLocation === "right"), [components]);
3131
- 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))) })] })) }));
3132
3139
  };
3133
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.
3134
3141
  const SidePaneTab = (props) => {
3135
3142
  const { location, id, isSelected, isFirst, isLast, dockOptions,
3136
3143
  // eslint-disable-next-line @typescript-eslint/naming-convention
3137
- icon: Icon, title, suppressTeachingMoment, } = props;
3138
- const classes = useStyles$M();
3144
+ icon: Icon, title, } = props;
3145
+ const classes = useStyles$N();
3139
3146
  const useTeachingMoment = useMemo(() => MakePopoverTeachingMoment(`Pane/${location}/${title ?? id}`), [title, id]);
3140
- const teachingMoment = useTeachingMoment(suppressTeachingMoment);
3147
+ const teachingMoment = useTeachingMoment(props.teachingMoment === false);
3141
3148
  const tabClass = mergeClasses(classes.tab, isSelected ? classes.selectedTab : classes.unselectedTab, isFirst ? classes.firstTab : undefined, isLast ? classes.lastTab : undefined);
3142
- 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: {
3143
3150
  children: jsx(Icon, {}),
3144
3151
  } }) }) }) })] }));
3145
3152
  };
@@ -3147,7 +3154,7 @@ const SidePaneTab = (props) => {
3147
3154
  // In "compact" mode, the tab list is integrated into the pane itself.
3148
3155
  // In "full" mode, the returned tab list is later injected into the toolbar.
3149
3156
  function usePane(location, defaultWidth, minWidth, sidePanes, onSelectSidePane, dockOperations, toolbarMode, topBarItems, bottomBarItems, initialCollapsed) {
3150
- const classes = useStyles$M();
3157
+ const classes = useStyles$N();
3151
3158
  const [topSelectedTab, setTopSelectedTab] = useState();
3152
3159
  const [bottomSelectedTab, setBottomSelectedTab] = useState();
3153
3160
  const [collapsed, setCollapsed] = useState(initialCollapsed);
@@ -3249,7 +3256,7 @@ function usePane(location, defaultWidth, minWidth, sidePanes, onSelectSidePane,
3249
3256
  setCollapsed(false);
3250
3257
  }, children: paneComponents.map((entry, index) => {
3251
3258
  const isSelected = selectedTab?.key === entry.key;
3252
- 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));
3253
3260
  }) }) })), toolbarMode === "full" && (jsx(Collapse, { visible: !isChildWindowOpen, orientation: "horizontal", children: expandCollapseButton }))] })) }));
3254
3261
  }, [location, collapsed, isChildWindowOpen, expandCollapseButton]);
3255
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).
@@ -3344,7 +3351,7 @@ function MakeShellServiceDefinition({ leftPaneDefaultWidth = 350, leftPaneMinWid
3344
3351
  expand: () => onCollapseChanged.notifyObservers({ location: "right", collapsed: false }),
3345
3352
  };
3346
3353
  const rootComponent = () => {
3347
- const classes = useStyles$M();
3354
+ const classes = useStyles$N();
3348
3355
  const [sidePaneDockOverrides, setSidePaneDockOverrides] = useSetting(SidePaneDockOverridesSettingDescriptor);
3349
3356
  // This function returns a promise that resolves after the dock change takes effect so that
3350
3357
  // we can then select the re-docked pane.
@@ -3579,7 +3586,7 @@ const SettingsServiceDefinition = {
3579
3586
  horizontalLocation: "right",
3580
3587
  verticalLocation: "top",
3581
3588
  order: 500,
3582
- suppressTeachingMoment: true,
3589
+ teachingMoment: false,
3583
3590
  content: () => {
3584
3591
  const sections = useOrderedObservableCollection(sectionsCollection);
3585
3592
  const sectionContent = useObservableCollection(sectionContentCollection);
@@ -3640,7 +3647,7 @@ const SelectionServiceDefinition = {
3640
3647
  selectedEntityObservable.notifyObservers();
3641
3648
  if (item) {
3642
3649
  const disposable = item;
3643
- if (disposable.dispose) {
3650
+ if (typeof disposable.dispose === "function") {
3644
3651
  disposedHook = InterceptFunction(disposable, "dispose", { afterCall: () => setSelectedItem(null) });
3645
3652
  }
3646
3653
  }
@@ -3685,7 +3692,7 @@ const PropertiesServiceDefinition = {
3685
3692
  horizontalLocation: "right",
3686
3693
  verticalLocation: "top",
3687
3694
  order: 100,
3688
- suppressTeachingMoment: true,
3695
+ teachingMoment: false,
3689
3696
  keepMounted: true,
3690
3697
  content: () => {
3691
3698
  const sections = useOrderedObservableCollection(sectionsCollection);
@@ -4005,7 +4012,7 @@ function useSceneExplorerDragDrop(options) {
4005
4012
 
4006
4013
  const SyntheticUniqueIds = new WeakMap();
4007
4014
  function GetEntityId(entity) {
4008
- if (entity.uniqueId !== undefined) {
4015
+ if ("uniqueId" in entity && typeof entity.uniqueId === "number") {
4009
4016
  return entity.uniqueId;
4010
4017
  }
4011
4018
  let id = SyntheticUniqueIds.get(entity);
@@ -4014,6 +4021,13 @@ function GetEntityId(entity) {
4014
4021
  }
4015
4022
  return id;
4016
4023
  }
4024
+ function IsEntityHidden(entity) {
4025
+ return ("reservedDataStore" in entity &&
4026
+ typeof entity.reservedDataStore === "object" &&
4027
+ entity.reservedDataStore &&
4028
+ "hidden" in entity.reservedDataStore &&
4029
+ entity.reservedDataStore.hidden === true);
4030
+ }
4017
4031
  function GetEntitySection(entityItem) {
4018
4032
  let current = entityItem;
4019
4033
  while (current.type === "entity") {
@@ -4082,7 +4096,7 @@ function CoerceEntityArray(entities, sort) {
4082
4096
  }
4083
4097
  return entities;
4084
4098
  }
4085
- const useStyles$L = makeStyles({
4099
+ const useStyles$M = makeStyles({
4086
4100
  rootDiv: {
4087
4101
  flex: 1,
4088
4102
  overflow: "hidden",
@@ -4190,14 +4204,14 @@ function MakeInlineCommandElement(command, isPlaceholder) {
4190
4204
  }
4191
4205
  const SceneTreeItem = (props) => {
4192
4206
  const { isSelected, select } = props;
4193
- const classes = useStyles$L();
4207
+ const classes = useStyles$M();
4194
4208
  const [compactMode] = useSetting(CompactModeSettingDescriptor);
4195
4209
  const treeItemLayoutClass = mergeClasses(classes.sceneTreeItemLayout, compactMode ? classes.treeItemLayoutCompact : undefined);
4196
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"));
4197
4211
  };
4198
4212
  const SectionTreeItem = (props) => {
4199
4213
  const { section, isFiltering, commandProviders, expandAll, collapseAll, isDropTarget, ...dropProps } = props;
4200
- const classes = useStyles$L();
4214
+ const classes = useStyles$M();
4201
4215
  const [compactMode] = useSetting(CompactModeSettingDescriptor);
4202
4216
  // Get the commands that apply to this section.
4203
4217
  const commands = useResource(useCallback(() => {
@@ -4214,7 +4228,7 @@ const SectionTreeItem = (props) => {
4214
4228
  };
4215
4229
  const EntityTreeItem = (props) => {
4216
4230
  const { entityItem, isSelected, select, isFiltering, commandProviders, expandAll, collapseAll, isDragging, isDropTarget, ...dragProps } = props;
4217
- const classes = useStyles$L();
4231
+ const classes = useStyles$M();
4218
4232
  const [compactMode] = useSetting(CompactModeSettingDescriptor);
4219
4233
  const hasChildren = !!entityItem.children?.length;
4220
4234
  const displayInfo = useResource(useCallback(() => {
@@ -4330,8 +4344,8 @@ const EntityTreeItem = (props) => {
4330
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] }) })] }));
4331
4345
  };
4332
4346
  const SceneExplorer = (props) => {
4333
- const classes = useStyles$L();
4334
- const { sections, entityCommandProviders, sectionCommandProviders, scene, selectedEntity } = props;
4347
+ const classes = useStyles$M();
4348
+ const { sections, entityCommandProviders, sectionCommandProviders, scene, selectedEntity = null } = props;
4335
4349
  const [openItems, setOpenItems] = useState(new Set());
4336
4350
  const [sceneVersion, setSceneVersion] = useState(0);
4337
4351
  const scrollViewRef = useRef(null);
@@ -4400,7 +4414,7 @@ const SceneExplorer = (props) => {
4400
4414
  scene: scene,
4401
4415
  };
4402
4416
  for (const section of sections) {
4403
- const rootEntities = section.getRootEntities().filter((entity) => !entity.reservedDataStore?.hidden);
4417
+ const rootEntities = section.getRootEntities().filter((entity) => !IsEntityHidden(entity));
4404
4418
  const sectionTreeItem = {
4405
4419
  type: "section",
4406
4420
  sectionName: section.displayName,
@@ -4432,7 +4446,7 @@ const SceneExplorer = (props) => {
4432
4446
  (treeItem) => {
4433
4447
  if (section.getEntityChildren) {
4434
4448
  const children = section.getEntityChildren(treeItem.entity);
4435
- return children.filter((child) => !child.reservedDataStore?.hidden).map((child) => createEntityTreeItemData(child, treeItem));
4449
+ return children.filter((child) => !IsEntityHidden(child)).map((child) => createEntityTreeItemData(child, treeItem));
4436
4450
  }
4437
4451
  return null;
4438
4452
  },
@@ -4623,7 +4637,7 @@ const SceneExplorerServiceDefinition = {
4623
4637
  icon: CubeTreeRegular,
4624
4638
  horizontalLocation: "left",
4625
4639
  verticalLocation: "top",
4626
- suppressTeachingMoment: true,
4640
+ teachingMoment: false,
4627
4641
  keepMounted: true,
4628
4642
  content: () => {
4629
4643
  const sections = useOrderedObservableCollection(sectionsCollection);
@@ -4784,7 +4798,7 @@ const DebugServiceDefinition = {
4784
4798
  horizontalLocation: "right",
4785
4799
  verticalLocation: "top",
4786
4800
  order: 200,
4787
- suppressTeachingMoment: true,
4801
+ teachingMoment: false,
4788
4802
  content: () => {
4789
4803
  const sections = useOrderedObservableCollection(sectionsCollection);
4790
4804
  const sectionContent = useObservableCollection(sectionContentCollection);
@@ -5953,7 +5967,7 @@ class CanvasGraphService {
5953
5967
  }
5954
5968
  }
5955
5969
 
5956
- const useStyles$K = makeStyles({
5970
+ const useStyles$L = makeStyles({
5957
5971
  canvas: {
5958
5972
  flexGrow: 1,
5959
5973
  width: "100%",
@@ -5962,7 +5976,7 @@ const useStyles$K = makeStyles({
5962
5976
  });
5963
5977
  const CanvasGraph = (props) => {
5964
5978
  const { collector, scene, layoutObservable, returnToPlayheadObservable, onVisibleRangeChangedObservable, initialGraphSize } = props;
5965
- const classes = useStyles$K();
5979
+ const classes = useStyles$L();
5966
5980
  const canvasRef = useRef(null);
5967
5981
  useEffect(() => {
5968
5982
  if (!canvasRef.current) {
@@ -6016,101 +6030,208 @@ const CanvasGraph = (props) => {
6016
6030
  return jsx("canvas", { className: classes.canvas, ref: canvasRef });
6017
6031
  };
6018
6032
 
6019
- function CoerceStepValue(step, isAltKeyPressed, isShiftKeyPressed) {
6020
- // When the alt key is pressed, decrease step by a factor of 10.
6021
- 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) {
6022
6036
  return step * 0.1;
6023
6037
  }
6024
- // When the shift key is pressed, increase step by a factor of 10.
6025
- if (isShiftKeyPressed) {
6038
+ // When the course key is pressed, increase step by a factor of 10.
6039
+ if (isCourseKeyPressed) {
6026
6040
  return step * 10;
6027
6041
  }
6028
6042
  return step;
6029
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
+ */
6030
6067
  const SpinButton = forwardRef((props, ref) => {
6031
- SpinButton.displayName = "SpinButton";
6032
- const classes = useInputStyles$1();
6068
+ SpinButton.displayName = "SpinButton2";
6069
+ const inputClasses = useInputStyles$1();
6070
+ const classes = useStyles$K();
6033
6071
  const { size } = useContext(ToolContext);
6034
6072
  const { min, max } = props;
6035
- const [value, setValue] = useState(props.value);
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);
6081
+ const stepPrecision = Math.max(0, CalculatePrecision(step));
6082
+ const [value, setValue] = useState(props.value ?? 0);
6036
6083
  const lastCommittedValue = useRef(props.value);
6037
- // When the input does not have keyboard focus
6038
- const isUnfocusedAltKeyPressed = useKeyState("Alt");
6039
- const isUnfocusedShiftKeyPressed = useKeyState("Shift");
6040
- // When the input does have keyboard focus
6041
- const [isFocusedAltKeyPressed, setIsFocusedAltKeyPressed] = useState(false);
6042
- const [isFocusedShiftKeyPressed, setIsFocusedShiftKeyPressed] = useState(false);
6043
- // 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
6044
- const step = CoerceStepValue(props.step ?? 1, isUnfocusedAltKeyPressed || isFocusedAltKeyPressed, isUnfocusedShiftKeyPressed || isFocusedShiftKeyPressed);
6045
- const precision = Math.min(4, Math.max(0, CalculatePrecision(step))); // Cap precision at 4 to avoid wild numbers
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]);
6046
6104
  useEffect(() => {
6047
- if (props.value !== lastCommittedValue.current) {
6105
+ if (!isDragging && props.value !== lastCommittedValue.current) {
6048
6106
  lastCommittedValue.current = props.value;
6049
- setValue(props.value); // Update local state when props.value changes
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);
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;
6071
6137
  }
6072
- };
6073
- const handleKeyDown = (event) => {
6074
- if (event.key === "Alt") {
6075
- 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;
6166
+ }
6167
+ }, [step]);
6168
+ const handleIconPointerMove = useCallback((e) => {
6169
+ if (!isDragging) {
6170
+ return;
6076
6171
  }
6077
- else if (event.key === "Shift") {
6078
- setIsFocusedShiftKeyPressed(true);
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.
6191
+ if (event.key === "Enter") {
6192
+ const committed = commitEditText(event.currentTarget.value);
6193
+ if (committed !== undefined) {
6194
+ inputRef.current?.blur();
6195
+ }
6196
+ }
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));
6079
6205
  }
6080
6206
  HandleKeyDown(event);
6081
- };
6082
- const handleKeyUp = (event) => {
6083
- event.stopPropagation(); // Prevent event propagation
6084
- if (event.key !== "Enter") {
6085
- if (event.key === "Alt") {
6086
- setIsFocusedAltKeyPressed(false);
6087
- }
6088
- else if (event.key === "Shift") {
6089
- setIsFocusedShiftKeyPressed(false);
6090
- }
6091
- // Allow arbitrary expressions, primarily for math operations (e.g. 10*60 for 10 minutes in seconds).
6092
- // Use Function constructor to safely evaluate the expression without allowing access to scope.
6093
- // If the expression is invalid, fallback to NaN which will be caught by validateValue and prevent committing.
6094
- const currVal = ((val) => {
6095
- try {
6096
- return Number(Function(`"use strict";return (${val})`)());
6097
- }
6098
- catch {
6099
- return NaN;
6100
- }
6101
- })(event.target.value);
6102
- setValue(currVal);
6103
- tryCommitValue(currVal);
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) {
6222
+ return;
6104
6223
  }
6105
- };
6106
- const id = useId("spin-button");
6107
- const mergedClassName = mergeClasses(classes.input, !validateValue(value) ? classes.invalid : "", props.className);
6108
- // Build input slot from inputClassName
6109
- const inputSlot = {
6110
- className: mergeClasses(classes.inputSlot, props.inputClassName),
6111
- };
6112
- const spinButton = (jsx(SpinButton$1, { ref: ref, ...props, appearance: "outline", input: inputSlot, step: step, id: id, size: size, precision: precision, displayValue: `${value.toFixed(precision)}${props.unit ? " " + props.unit : ""}`, value: value, onChange: handleChange, onKeyDown: handleKeyDown, onKeyUp: handleKeyUp, onBlur: HandleOnBlur, className: mergedClassName }));
6113
- 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);
6114
6235
  });
6115
6236
 
6116
6237
  const TextInput = (props) => {
@@ -6792,7 +6913,7 @@ const StatsServiceDefinition = {
6792
6913
  horizontalLocation: "right",
6793
6914
  verticalLocation: "top",
6794
6915
  order: 300,
6795
- suppressTeachingMoment: true,
6916
+ teachingMoment: false,
6796
6917
  content: () => {
6797
6918
  const sections = useOrderedObservableCollection(sectionsCollection);
6798
6919
  const sectionContent = useObservableCollection(sectionContentCollection);
@@ -6881,7 +7002,7 @@ const ToolsServiceDefinition = {
6881
7002
  horizontalLocation: "right",
6882
7003
  verticalLocation: "top",
6883
7004
  order: 400,
6884
- suppressTeachingMoment: true,
7005
+ teachingMoment: false,
6885
7006
  content: () => {
6886
7007
  const sections = useOrderedObservableCollection(sectionsCollection);
6887
7008
  const sectionContent = useObservableCollection(sectionContentCollection);
@@ -6899,12 +7020,337 @@ const ToolsServiceDefinition = {
6899
7020
  },
6900
7021
  };
6901
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
+
6902
7348
  const GizmoServiceIdentity = Symbol("GizmoService");
6903
7349
  const GizmoServiceDefinition = {
6904
7350
  friendlyName: "Gizmo Service",
6905
7351
  produces: [GizmoServiceIdentity],
6906
- consumes: [SceneContextIdentity, SelectionServiceIdentity],
6907
- factory: (sceneContext, selectionService) => {
7352
+ consumes: [SceneContextIdentity, SelectionServiceIdentity, WatcherServiceIdentity],
7353
+ factory: (sceneContext, selectionService, watcherService) => {
6908
7354
  // Ref-counted utility layers, shared across consumers.
6909
7355
  const utilityLayers = new WeakMap();
6910
7356
  const getUtilityLayer = (scene, layer = "default") => {
@@ -7003,13 +7449,11 @@ const GizmoServiceDefinition = {
7003
7449
  currentKeepDepthUtilityLayerRef = null;
7004
7450
  };
7005
7451
  gm.coordinatesMode = coordinatesModeState;
7006
- coordinatesModeInterceptToken = InterceptProperty(gm, "coordinatesMode", {
7007
- afterSet: (value) => {
7008
- if (value !== coordinatesModeState) {
7009
- coordinatesModeState = value;
7010
- coordinatesModeObservable.notifyObservers();
7011
- }
7012
- },
7452
+ coordinatesModeInterceptToken = watcherService.watchProperty(gm, "coordinatesMode", (value) => {
7453
+ if (value !== coordinatesModeState) {
7454
+ coordinatesModeState = value;
7455
+ coordinatesModeObservable.notifyObservers();
7456
+ }
7013
7457
  });
7014
7458
  currentGizmoManager = gm;
7015
7459
  }
@@ -7159,146 +7603,40 @@ const GizmoServiceDefinition = {
7159
7603
  sceneObserver.remove();
7160
7604
  selectionObserver.remove();
7161
7605
  destroyGizmoManager();
7162
- gizmoModeObservable.clear();
7163
- coordinatesModeObservable.clear();
7164
- cameraGizmoObservable.clear();
7165
- },
7166
- };
7167
- },
7168
- };
7169
-
7170
- const BabylonWebResources = {
7171
- homepage: "https://www.babylonjs.com",
7172
- repository: "https://github.com/BabylonJS/Babylon.js",
7173
- bugs: "https://github.com/BabylonJS/Babylon.js/issues",
7174
- };
7175
- /**
7176
- * Well-known default built in extensions for the Inspector.
7177
- */
7178
- const DefaultInspectorExtensionFeed = new BuiltInsExtensionFeed("Inspector", [
7179
- {
7180
- name: "Quick Creation Tools (Preview)",
7181
- description: "Adds a new panel for easy creation of various Babylon assets. This is a WIP extension...expect changes!",
7182
- keywords: ["creation", "tools"],
7183
- ...BabylonWebResources,
7184
- author: { name: "Babylon.js", forumUserName: "" },
7185
- getExtensionModuleAsync: async () => await import('./quickCreateToolsService-BuEys230.js'),
7186
- },
7187
- {
7188
- name: "Reflector",
7189
- description: "Connects to the Reflector Bridge for real-time scene synchronization with the Babylon.js Sandbox.",
7190
- keywords: ["reflector", "bridge", "sync", "sandbox", "tools"],
7191
- ...BabylonWebResources,
7192
- author: { name: "Babylon.js", forumUserName: "" },
7193
- getExtensionModuleAsync: async () => await import('./reflectorService-Dwsb8ijf.js'),
7194
- },
7195
- ]);
7196
-
7197
- const useSyncedSliderStyles = makeStyles({
7198
- container: { display: "flex", minWidth: 0 },
7199
- syncedSlider: {
7200
- flex: "1 1 0",
7201
- flexDirection: "row",
7202
- display: "flex",
7203
- alignItems: "center",
7204
- minWidth: 0,
7205
- },
7206
- slider: {
7207
- flex: "1 1 auto",
7208
- minWidth: "75px",
7209
- maxWidth: "75px",
7210
- },
7211
- compactSlider: {
7212
- flex: "1 1 auto",
7213
- minWidth: "50px", // Allow shrinking for compact mode
7214
- maxWidth: "75px",
7215
- },
7216
- growSlider: {
7217
- flex: "1 1 auto",
7218
- minWidth: "50px",
7219
- // No maxWidth - slider grows to fill available space
7220
- },
7221
- compactSpinButton: {
7222
- width: "65px",
7223
- minWidth: "65px",
7224
- maxWidth: "65px",
7225
- },
7226
- compactSpinButtonInput: {
7227
- minWidth: "0",
7228
- },
7229
- });
7230
- /**
7231
- * Component which synchronizes a slider and an input field, allowing the user to change the value using either control
7232
- * @param props
7233
- * @returns SyncedSlider component
7234
- */
7235
- const SyncedSliderInput = (props) => {
7236
- SyncedSliderInput.displayName = "SyncedSliderInput";
7237
- const { infoLabel, ...passthroughProps } = props;
7238
- const classes = useSyncedSliderStyles();
7239
- const { size } = useContext(ToolContext);
7240
- const [value, setValue] = useState(props.value ?? 0);
7241
- const pendingValueRef = useRef(undefined);
7242
- const isDraggingRef = useRef(false);
7243
- // NOTE: The Fluent slider will add tick marks if the step prop is anything other than undefined.
7244
- // To avoid this, we scale the min/max based on the step so we can always make step undefined.
7245
- // The actual step size in the Fluent slider is 1 when it is ste to undefined.
7246
- const min = props.min ?? 0;
7247
- const max = props.max ?? 100;
7248
- const step = props.step ?? 1;
7249
- useEffect(() => {
7250
- !isDraggingRef.current && setValue(props.value ?? 0); // Update local state when props.value changes as long as user is not actively dragging
7251
- }, [props.value]);
7252
- const handleSliderChange = (_, data) => {
7253
- const newValue = data.value * step;
7254
- setValue(newValue);
7255
- if (props.notifyOnlyOnRelease) {
7256
- // Store the value but don't notify parent yet
7257
- pendingValueRef.current = newValue;
7258
- }
7259
- else {
7260
- // Notify parent as slider changes
7261
- props.onChange(newValue);
7262
- }
7263
- };
7264
- const handleSliderPointerDown = () => {
7265
- isDraggingRef.current = true;
7266
- };
7267
- const handleSliderPointerUp = () => {
7268
- if (props.notifyOnlyOnRelease && isDraggingRef.current && pendingValueRef.current !== undefined) {
7269
- props.onChange(pendingValueRef.current);
7270
- pendingValueRef.current = undefined;
7271
- }
7272
- isDraggingRef.current = false;
7273
- };
7274
- const handleInputChange = (value) => {
7275
- setValue(value);
7276
- props.onChange(value); // Input always updates immediately
7277
- };
7278
- const hasSlider = props.min !== undefined && props.max !== undefined;
7279
- // Determine Slider className based on props
7280
- const getSliderClassName = () => {
7281
- if (props.growSlider) {
7282
- return classes.growSlider;
7283
- }
7284
- if (props.compact) {
7285
- return classes.compactSlider;
7286
- }
7287
- return classes.slider;
7288
- };
7289
- 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 })] })] }));
7606
+ gizmoModeObservable.clear();
7607
+ coordinatesModeObservable.clear();
7608
+ cameraGizmoObservable.clear();
7609
+ },
7610
+ };
7611
+ },
7290
7612
  };
7291
7613
 
7614
+ const BabylonWebResources = {
7615
+ homepage: "https://www.babylonjs.com",
7616
+ repository: "https://github.com/BabylonJS/Babylon.js",
7617
+ bugs: "https://github.com/BabylonJS/Babylon.js/issues",
7618
+ };
7292
7619
  /**
7293
- * Renders a simple wrapper around the SyncedSliderInput
7294
- * @param props
7295
- * @returns
7620
+ * Well-known default built in extensions for the Inspector.
7296
7621
  */
7297
- const SyncedSliderPropertyLine = forwardRef((props, ref) => {
7298
- SyncedSliderPropertyLine.displayName = "SyncedSliderPropertyLine";
7299
- const { label, description, ...sliderProps } = props;
7300
- return (jsx(PropertyLine, { ref: ref, ...props, children: jsx(SyncedSliderInput, { ...sliderProps }) }));
7301
- });
7622
+ const DefaultInspectorExtensionFeed = new BuiltInsExtensionFeed("Inspector", [
7623
+ {
7624
+ name: "Quick Creation Tools (Preview)",
7625
+ description: "Adds a new panel for easy creation of various Babylon assets. This is a WIP extension...expect changes!",
7626
+ keywords: ["creation", "tools"],
7627
+ ...BabylonWebResources,
7628
+ author: { name: "Babylon.js", forumUserName: "" },
7629
+ getExtensionModuleAsync: async () => await import('./quickCreateToolsService-DHfs_EZ6.js'),
7630
+ },
7631
+ {
7632
+ name: "Reflector",
7633
+ description: "Connects to the Reflector Bridge for real-time scene synchronization with the Babylon.js Sandbox.",
7634
+ keywords: ["reflector", "bridge", "sync", "sandbox", "tools"],
7635
+ ...BabylonWebResources,
7636
+ author: { name: "Babylon.js", forumUserName: "" },
7637
+ getExtensionModuleAsync: async () => await import('./reflectorService-DEPuGTAZ.js'),
7638
+ },
7639
+ ]);
7302
7640
 
7303
7641
  /**
7304
7642
  * Reusable component which renders a color property line containing a label, colorPicker popout, and expandable RGBA values
@@ -7334,30 +7672,6 @@ const ColorSliders = ({ color, onSliderChange }) => (jsxs(Fragment, { children:
7334
7672
  const Color3PropertyLine = ColorPropertyLine;
7335
7673
  const Color4PropertyLine = ColorPropertyLine;
7336
7674
 
7337
- const useStyles$F = makeStyles({
7338
- dropdown: {
7339
- ...UniformWidthStyling,
7340
- },
7341
- });
7342
- /**
7343
- * Wraps a dropdown in a property line
7344
- * @param props - PropertyLineProps and DropdownProps
7345
- * @returns property-line wrapped dropdown
7346
- */
7347
- const DropdownPropertyLine = forwardRef((props, ref) => {
7348
- DropdownPropertyLine.displayName = "DropdownPropertyLine";
7349
- const classes = useStyles$F();
7350
- return (jsx(PropertyLine, { ...props, ref: ref, children: jsx(Dropdown, { ...props, className: classes.dropdown }) }));
7351
- });
7352
- /**
7353
- * Dropdown component for number values.
7354
- */
7355
- const NumberDropdownPropertyLine = DropdownPropertyLine;
7356
- /**
7357
- * Dropdown component for string values
7358
- */
7359
- const StringDropdownPropertyLine = DropdownPropertyLine;
7360
-
7361
7675
  /**
7362
7676
  * Wraps a text input in a property line
7363
7677
  * @param props - PropertyLineProps and InputProps
@@ -7402,21 +7716,21 @@ const TensorPropertyLine = (props) => {
7402
7716
  useEffect(() => {
7403
7717
  setVector(props.value);
7404
7718
  }, [props.value, props.expandedContent]);
7405
- 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)}` : ""}]` }) }));
7406
7720
  };
7407
- 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 }))] }));
7408
7722
  const ToDegreesConverter = { from: Tools.ToDegrees, to: Tools.ToRadians };
7409
7723
  const RotationVectorPropertyLine = (props) => {
7410
7724
  RotationVectorPropertyLine.displayName = "RotationVectorPropertyLine";
7411
- const min = props.useDegrees ? 0 : undefined;
7412
- const max = props.useDegrees ? 360 : undefined;
7413
- 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 }));
7414
7728
  };
7415
7729
  const QuaternionPropertyLineInternal = TensorPropertyLine;
7416
7730
  const QuaternionPropertyLine = (props) => {
7417
7731
  QuaternionPropertyLine.displayName = "QuaternionPropertyLine";
7418
- const min = props.useDegrees ? 0 : undefined;
7419
- const max = props.useDegrees ? 360 : undefined;
7732
+ const step = props.useDegrees ? 1 : 0.01;
7733
+ const precision = props.useDegrees ? 1 : 2;
7420
7734
  const [quat, setQuat] = useState(props.value);
7421
7735
  useEffect(() => {
7422
7736
  setQuat(props.value);
@@ -7431,7 +7745,7 @@ const QuaternionPropertyLine = (props) => {
7431
7745
  const quat = Quaternion.FromEulerAngles(val.x, val.y, val.z);
7432
7746
  onQuatChange(quat);
7433
7747
  };
7434
- 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 }));
7435
7749
  };
7436
7750
  const Vector2PropertyLine = TensorPropertyLine;
7437
7751
  const Vector3PropertyLine = TensorPropertyLine;
@@ -7946,7 +8260,7 @@ const ThemeSelectorServiceDefinition = {
7946
8260
  key: "ThemeSelector",
7947
8261
  horizontalLocation: "right",
7948
8262
  verticalLocation: "top",
7949
- suppressTeachingMoment: true,
8263
+ teachingMoment: false,
7950
8264
  order: -300,
7951
8265
  component: () => {
7952
8266
  const classes = useStyles$E();
@@ -8011,7 +8325,7 @@ function MakeModularTool(options) {
8011
8325
  const [requiredExtensions, setRequiredExtensions] = useState();
8012
8326
  const [requiredExtensionsDeferred, setRequiredExtensionsDeferred] = useState();
8013
8327
  const [extensionInstallError, setExtensionInstallError] = useState();
8014
- const [rootComponent, setRootComponent] = useState();
8328
+ const [bootstrapServices, setBootstrapServices] = useState();
8015
8329
  // This is the main async initialization.
8016
8330
  useEffect(() => {
8017
8331
  const initializeExtensionManagerAsync = async () => {
@@ -8022,17 +8336,22 @@ function MakeModularTool(options) {
8022
8336
  produces: [SettingsStoreIdentity],
8023
8337
  factory: () => settingsStore,
8024
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);
8025
8344
  // Register the shell service (top level toolbar/side pane UI layout).
8026
8345
  await serviceContainer.addServiceAsync(MakeShellServiceDefinition(options));
8027
- // 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.
8028
8347
  await serviceContainer.addServiceAsync({
8029
- friendlyName: "Root Component Bootstrapper",
8030
- consumes: [RootComponentServiceIdentity],
8031
- factory: (rootComponentService) => {
8348
+ friendlyName: "Service Bootstrapper",
8349
+ consumes: [RootComponentServiceIdentity, WatcherServiceIdentity],
8350
+ factory: (rootComponentService, watcherService) => {
8032
8351
  // Use function syntax for the state setter since the root component may be a function component.
8033
- setRootComponent(() => rootComponentService.rootComponent);
8352
+ setBootstrapServices({ rootComponentService, watcherService });
8034
8353
  return {
8035
- dispose: () => setRootComponent(undefined),
8354
+ dispose: () => setBootstrapServices(undefined),
8036
8355
  };
8037
8356
  },
8038
8357
  });
@@ -8044,7 +8363,7 @@ function MakeModularTool(options) {
8044
8363
  }
8045
8364
  // Register the extension list service (for browsing/installing extensions) if extension feeds are provided.
8046
8365
  if (extensionFeeds.length > 0) {
8047
- const { ExtensionListServiceDefinition } = await import('./extensionsListService-Dj1XtRzF.js');
8366
+ const { ExtensionListServiceDefinition } = await import('./extensionsListService-Duej2zkq.js');
8048
8367
  await serviceContainer.addServiceAsync(ExtensionListServiceDefinition);
8049
8368
  }
8050
8369
  // Register all external services (that make up a unique tool).
@@ -8111,12 +8430,17 @@ function MakeModularTool(options) {
8111
8430
  setExtensionInstallError(undefined);
8112
8431
  }, [setExtensionInstallError]);
8113
8432
  // Show a spinner until a main view has been set.
8114
- // eslint-disable-next-line @typescript-eslint/naming-convention
8115
- const Content = rootComponent ?? (() => jsx(Spinner, { className: classes.spinner }));
8116
- return (
8117
- // Expose the settings store as a React context so that UI components can read/write
8118
- // settings without the ISettingsService needing to be explicitly passed around.
8119
- 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
+ }
8120
8444
  };
8121
8445
  // Set the container element to be a flex container so that the tool can be displayed properly.
8122
8446
  const originalContainerElementDisplay = containerElement.style.display;
@@ -8195,12 +8519,103 @@ const GizmoToolbarServiceDefinition = {
8195
8519
  key: "Gizmo Toolbar",
8196
8520
  verticalLocation: "top",
8197
8521
  horizontalLocation: "left",
8198
- suppressTeachingMoment: true,
8522
+ teachingMoment: false,
8199
8523
  component: () => jsx(GizmoToolbar, { gizmoService: gizmoService, sceneContext: sceneContext }),
8200
8524
  });
8201
8525
  },
8202
8526
  };
8203
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
+
8204
8619
  const useStyles$B = makeStyles({
8205
8620
  badge: {
8206
8621
  margin: tokens.spacingHorizontalXXS,
@@ -8215,7 +8630,8 @@ const MiniStatsServiceDefinition = {
8215
8630
  key: "Mini Stats",
8216
8631
  verticalLocation: "bottom",
8217
8632
  horizontalLocation: "right",
8218
- suppressTeachingMoment: true,
8633
+ order: 300 /* DefaultToolbarItemOrder.FrameRate */,
8634
+ teachingMoment: false,
8219
8635
  component: () => {
8220
8636
  const classes = useStyles$B();
8221
8637
  const scene = useObservableState(useCallback(() => sceneContext.currentScene, [sceneContext.currentScene]), sceneContext.currentSceneObservable);
@@ -8245,8 +8661,7 @@ const CurveEditorProvider = (props) => {
8245
8661
  const [activeAnimations, setActiveAnimations] = useState([]);
8246
8662
  const [activeChannels, setActiveChannels] = useState({});
8247
8663
  const [activeKeyPoints, setActiveKeyPoints] = useState(null);
8248
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
8249
- const [mainKeyPoint, _setMainKeyPoint] = useState(null);
8664
+ const [mainKeyPoint, setMainKeyPoint] = useState(null);
8250
8665
  const [activeFrame, setActiveFrame] = useState(0);
8251
8666
  const [fromKey, setFromKey] = useState(0);
8252
8667
  const [toKey, setToKey] = useState(100);
@@ -8498,6 +8913,7 @@ const CurveEditorProvider = (props) => {
8498
8913
  setReferenceMaxFrame,
8499
8914
  setFocusedInput,
8500
8915
  setActiveKeyPoints,
8916
+ setMainKeyPoint,
8501
8917
  setActiveChannels,
8502
8918
  play,
8503
8919
  stop,
@@ -8616,12 +9032,16 @@ const TopBar = () => {
8616
9032
  });
8617
9033
  const onActiveKeyPointChangedObserver = observables.onActiveKeyPointChanged.add(() => {
8618
9034
  const numKeys = state.activeKeyPoints?.length || 0;
8619
- // TODO: Properly type KeyPointComponent to access curve.animation
8620
- const numAnims = numKeys;
8621
- const frameEnabled = (numKeys === 1 && numAnims === 1) || (numKeys > 1 && numAnims > 1);
9035
+ const numAnims = state.activeKeyPoints ? new Set(state.activeKeyPoints.map((kp) => kp.curve.animation.uniqueId)).size : 0;
9036
+ const hasActiveQuaternion = state.activeKeyPoints?.some((kp) => kp.curve.animation.dataType === Animation.ANIMATIONTYPE_QUATERNION) ?? false;
9037
+ const frameEnabled = ((numKeys === 1 && numAnims === 1) || (numKeys > 1 && numAnims > 1)) && !hasActiveQuaternion;
8622
9038
  setFrameControlEnabled(frameEnabled);
8623
- setValueControlEnabled(numKeys > 0);
8624
- // Don't reset values here - they are set by onFrameSet/onValueSet observers
9039
+ setValueControlEnabled(numKeys > 0 && !hasActiveQuaternion);
9040
+ // Reset values when no keys are selected
9041
+ if (numKeys === 0) {
9042
+ setKeyFrameValue(null);
9043
+ setKeyValue(null);
9044
+ }
8625
9045
  });
8626
9046
  return () => {
8627
9047
  observables.onFrameSet.remove(onFrameSetObserver);
@@ -8938,6 +9358,7 @@ const useStyles$x = makeStyles({
8938
9358
  display: "flex",
8939
9359
  flexDirection: "column",
8940
9360
  gap: tokens.spacingVerticalM,
9361
+ minWidth: "150px",
8941
9362
  },
8942
9363
  header: {
8943
9364
  display: "flex",
@@ -8973,13 +9394,16 @@ const useStyles$x = makeStyles({
8973
9394
  const ANIMATION_TYPES = ["Float", "Vector2", "Vector3", "Quaternion", "Color3", "Color4"];
8974
9395
  const LOOP_MODES = ["Cycle", "Relative", "Relative from current", "Constant"];
8975
9396
  const MODES = ["List", "Custom"];
9397
+ const ModeOptions = MODES.map((m) => ({ label: m, value: m }));
9398
+ const AnimationTypeOptions = ANIMATION_TYPES.map((t) => ({ label: t, value: t }));
9399
+ const LoopModeOptions = LOOP_MODES.map((lm) => ({ label: lm, value: lm }));
8976
9400
  /**
8977
9401
  * Panel for adding new animations
8978
9402
  * @returns The add animation panel component
8979
9403
  */
8980
9404
  const AddAnimationPanel = ({ onClose }) => {
8981
9405
  const styles = useStyles$x();
8982
- const { state, observables } = useCurveEditor();
9406
+ const { state, actions, observables } = useCurveEditor();
8983
9407
  const [name, setName] = useState("");
8984
9408
  const [mode, setMode] = useState("List");
8985
9409
  const [customProperty, setCustomProperty] = useState("");
@@ -9184,11 +9608,15 @@ const AddAnimationPanel = ({ onClose }) => {
9184
9608
  state.target.animations = [...state.target.animations, animation];
9185
9609
  }
9186
9610
  }
9187
- // Close first so AnimationList mounts, then notify
9611
+ // Auto-select the newly created animation
9612
+ actions.setActiveAnimations([animation]);
9613
+ actions.resetAllActiveChannels();
9614
+ // Close panel, then notify so listeners (e.g. AnimationList) can react
9188
9615
  onClose();
9189
9616
  observables.onAnimationsLoaded.notifyObservers();
9190
- }, [name, currentProperty, currentType, loopMode, fps, minFrame, maxFrame, state, observables, onClose]);
9191
- return (jsxs("div", { className: styles.root, children: [jsx("div", { className: styles.header, children: jsx("div", { className: styles.title, children: "Add Animation" }) }), jsxs("div", { className: styles.form, children: [jsxs("div", { className: styles.row, children: [jsx(Label, { children: "Display Name" }), jsx(Input, { value: name, onChange: (_, data) => setName(data.value), placeholder: "Animation name" })] }), jsxs("div", { className: styles.row, children: [jsx(Label, { children: "Mode" }), jsx(Dropdown$1, { value: mode, selectedOptions: [mode], onOptionSelect: (_, data) => setMode(data.optionValue), disabled: properties.length === 0, children: MODES.map((m) => (jsx(Option, { value: m, children: m }, m))) })] }), jsxs("div", { className: styles.row, children: [jsx(Label, { children: "Property" }), isCustomMode ? (jsx(Input, { value: customProperty, onChange: (_, data) => setCustomProperty(data.value), placeholder: "e.g., position, rotation, scaling" })) : (jsx(Dropdown$1, { value: selectedProperty, selectedOptions: [selectedProperty], onOptionSelect: (_, data) => setSelectedProperty(data.optionValue), children: properties.map((prop) => (jsx(Option, { value: prop, children: prop }, prop))) }))] }), jsxs("div", { className: styles.row, children: [jsx(Label, { children: "Type" }), isCustomMode ? (jsx(Dropdown$1, { value: animationType, selectedOptions: [animationType], onOptionSelect: (_, data) => setAnimationType(data.optionValue), children: ANIMATION_TYPES.map((type) => (jsx(Option, { value: type, children: type }, type))) })) : (jsx("div", { className: styles.typeDisplay, children: inferredType }))] }), jsxs("div", { className: styles.row, children: [jsx(Label, { children: "Loop Mode" }), jsx(Dropdown$1, { value: loopMode, selectedOptions: [loopMode], onOptionSelect: (_, data) => setLoopMode(data.optionValue), children: LOOP_MODES.map((lm) => (jsx(Option, { value: lm, children: lm }, lm))) })] })] }), jsxs("div", { className: styles.buttons, children: [jsx(Button, { appearance: "primary", onClick: createAnimation, disabled: !isValid, label: "Create" }), jsx(Button, { appearance: "subtle", onClick: onClose, label: "Cancel" })] })] }));
9617
+ observables.onActiveAnimationChanged.notifyObservers({});
9618
+ }, [name, currentProperty, currentType, loopMode, fps, minFrame, maxFrame, state.target, actions, observables, onClose]);
9619
+ return (jsxs("div", { className: styles.root, children: [jsx("div", { className: styles.header, children: jsx("div", { className: styles.title, children: "Add Animation" }) }), jsxs("div", { className: styles.form, children: [jsxs("div", { className: styles.row, children: [jsx(Label, { children: "Display Name" }), jsx(TextInput, { value: name, onChange: setName })] }), jsx("div", { className: styles.row, children: jsx(StringDropdown, { value: mode, onChange: (val) => setMode(val), options: ModeOptions, disabled: properties.length === 0, infoLabel: { label: "Mode" } }) }), jsxs("div", { className: styles.row, children: [jsx(Label, { children: "Property" }), isCustomMode ? (jsx(TextInput, { value: customProperty, onChange: setCustomProperty })) : (jsx(StringDropdown, { value: selectedProperty, onChange: (val) => setSelectedProperty(val), options: properties.map((p) => ({ label: p, value: p })) }))] }), jsxs("div", { className: styles.row, children: [jsx(Label, { children: "Type" }), isCustomMode ? (jsx(StringDropdown, { value: animationType, onChange: (val) => setAnimationType(val), options: AnimationTypeOptions })) : (jsx("div", { className: styles.typeDisplay, children: inferredType }))] }), jsx("div", { className: styles.row, children: jsx(StringDropdown, { value: loopMode, onChange: (val) => setLoopMode(val), options: LoopModeOptions, infoLabel: { label: "Loop Mode" } }) })] }), jsxs("div", { className: styles.buttons, children: [jsx(Button, { appearance: "primary", onClick: createAnimation, disabled: !isValid, label: "Create" }), jsx(Button, { appearance: "subtle", onClick: onClose, label: "Cancel" })] })] }));
9192
9620
  };
9193
9621
 
9194
9622
  const useStyles$w = makeStyles({
@@ -9616,6 +10044,7 @@ class CurveData {
9616
10044
  constructor(color, animation, property, tangentBuilder, setDefaultInTangent, setDefaultOutTangent) {
9617
10045
  this.keys = [];
9618
10046
  this.onDataUpdatedObservable = new Observable();
10047
+ this.siblings = [];
9619
10048
  this.color = color;
9620
10049
  this.animation = animation;
9621
10050
  this.property = property;
@@ -9809,6 +10238,13 @@ class CurveData {
9809
10238
  const originalKey = this.animation.getKeys()[keyId];
9810
10239
  originalKey.frame = frame;
9811
10240
  this.keys[keyId].frame = frame;
10241
+ // Sync frame to all sibling curves (same animation, different property)
10242
+ for (const sibling of this.siblings) {
10243
+ if (sibling !== this && sibling.keys[keyId]) {
10244
+ sibling.keys[keyId].frame = frame;
10245
+ sibling.onDataUpdatedObservable.notifyObservers();
10246
+ }
10247
+ }
9812
10248
  this.onDataUpdatedObservable.notifyObservers();
9813
10249
  }
9814
10250
  updateKeyValue(keyId, value) {
@@ -9833,6 +10269,8 @@ CurveData.TangentLength = 50;
9833
10269
  */
9834
10270
  const Curve = ({ curve, convertX, convertY }) => {
9835
10271
  const isQuaternion = curve.animation.dataType === Animation.ANIMATIONTYPE_QUATERNION;
10272
+ // Derive path data, recomputing whenever the curve's key data changes
10273
+ const pathData = useObservableState(useCallback(() => curve.getPathData(convertX, convertY), [curve, convertX, convertY]), curve.onDataUpdatedObservable);
9836
10274
  // Path style - same as v1
9837
10275
  const pathStyle = {
9838
10276
  stroke: curve.color,
@@ -9843,7 +10281,7 @@ const Curve = ({ curve, convertX, convertY }) => {
9843
10281
  pathStyle["strokeDasharray"] = "5";
9844
10282
  pathStyle["strokeOpacity"] = "0.5";
9845
10283
  }
9846
- return (jsx("svg", { style: { cursor: "pointer", overflow: "auto" }, children: jsx("path", { d: curve.getPathData(convertX, convertY), style: pathStyle }) }));
10284
+ return (jsx("svg", { style: { cursor: "pointer", overflow: "auto" }, children: jsx("path", { d: pathData, style: pathStyle }) }));
9847
10285
  };
9848
10286
 
9849
10287
  // Inline SVG data URIs for key point icons
@@ -9921,6 +10359,9 @@ const KeyPointComponent = (props) => {
9921
10359
  if (isSelected()) {
9922
10360
  // This keypoint is directly selected
9923
10361
  setSelectedState(SelectionState.Selected);
10362
+ // Notify frame/value observers so the top bar displays correct values (like v1's _onActiveKeyPointChanged)
10363
+ observables.onFrameSet.notifyObservers(invertX(currentXRef.current));
10364
+ observables.onValueSet.notifyObservers(invertY(currentYRef.current));
9924
10365
  }
9925
10366
  else if (state.activeKeyPoints) {
9926
10367
  // Check if a sibling (same keyId, different curve, same animation) is selected
@@ -9943,8 +10384,8 @@ const KeyPointComponent = (props) => {
9943
10384
  setSelectedState(SelectionState.None);
9944
10385
  setTangentSelectedIndex(-1);
9945
10386
  }
9946
- }, [state.activeKeyPoints, state.mainKeyPoint, curve, keyId, isSelected, curvesMatch]);
9947
- // Extract slope from a tangent vector (matches v1's _extractSlope)
10387
+ }, [state.activeKeyPoints, state.mainKeyPoint, curve, keyId, isSelected, curvesMatch, observables, invertX, invertY]);
10388
+ // Extract slope from a tangent vector
9948
10389
  const extractSlope = useCallback((vec, storedLength, isIn) => {
9949
10390
  const keys = curve.keys;
9950
10391
  const keyValue = keys[keyId].value;
@@ -9960,10 +10401,13 @@ const KeyPointComponent = (props) => {
9960
10401
  const currentPosition = vec.clone();
9961
10402
  currentPosition.normalize();
9962
10403
  currentPosition.scaleInPlace(storedLength);
9963
- const value = isIn ? keyValue - invertY(currentPosition.y + currentY) : invertY(currentPosition.y + currentY) - keyValue;
9964
- const frame = isIn ? keyFrame - invertX(currentPosition.x + currentX) : invertX(currentPosition.x + currentX) - keyFrame;
10404
+ // Use refs for current position to avoid stale closure during drag
10405
+ const cx = currentXRef.current;
10406
+ const cy = currentYRef.current;
10407
+ const value = isIn ? keyValue - invertY(currentPosition.y + cy) : invertY(currentPosition.y + cy) - keyValue;
10408
+ const frame = isIn ? keyFrame - invertX(currentPosition.x + cx) : invertX(currentPosition.x + cx) - keyFrame;
9965
10409
  return value / frame;
9966
- }, [curve, keyId, invertX, invertY, currentX, currentY]);
10410
+ }, [curve, keyId, invertX, invertY]);
9967
10411
  // Tangent operations
9968
10412
  const flattenTangent = useCallback(() => {
9969
10413
  // First update the interpolation mode to NONE without triggering observers
@@ -9982,6 +10426,8 @@ const KeyPointComponent = (props) => {
9982
10426
  curve.updateOutTangentFromControlPoint(keyId, 0);
9983
10427
  }
9984
10428
  }
10429
+ // Notify curve to re-render path
10430
+ curve.onDataUpdatedObservable.notifyObservers();
9985
10431
  setForceUpdate((v) => v + 1);
9986
10432
  }, [keyId, tangentSelectedIndex, curve]);
9987
10433
  const linearTangent = useCallback(() => {
@@ -10095,6 +10541,46 @@ const KeyPointComponent = (props) => {
10095
10541
  observables.onSelectionRectangleMoved.remove(observer);
10096
10542
  };
10097
10543
  }, [observables, curve, keyId, actions]);
10544
+ // Handle frame manually entered from top bar
10545
+ useEffect(() => {
10546
+ const observer = observables.onFrameManuallyEntered.add((newValue) => {
10547
+ if (selectedState === SelectionState.None) {
10548
+ return;
10549
+ }
10550
+ let newX = convertX(newValue);
10551
+ // Clamp to neighbors
10552
+ const previousX = getPreviousX();
10553
+ const nextX = getNextX();
10554
+ if (previousX !== null) {
10555
+ newX = Math.max(previousX, newX);
10556
+ }
10557
+ if (nextX !== null) {
10558
+ newX = Math.min(nextX, newX);
10559
+ }
10560
+ const frame = invertX(newX);
10561
+ currentXRef.current = newX;
10562
+ setCurrentX(newX);
10563
+ onFrameValueChanged(frame);
10564
+ });
10565
+ return () => {
10566
+ observables.onFrameManuallyEntered.remove(observer);
10567
+ };
10568
+ }, [observables, selectedState, convertX, invertX, getPreviousX, getNextX, onFrameValueChanged]);
10569
+ // Handle value manually entered from top bar
10570
+ useEffect(() => {
10571
+ const observer = observables.onValueManuallyEntered.add((newValue) => {
10572
+ if (selectedState !== SelectionState.Selected) {
10573
+ return;
10574
+ }
10575
+ const newY = convertY(newValue);
10576
+ currentYRef.current = newY;
10577
+ setCurrentY(newY);
10578
+ onKeyValueChanged(newValue);
10579
+ });
10580
+ return () => {
10581
+ observables.onValueManuallyEntered.remove(observer);
10582
+ };
10583
+ }, [observables, selectedState, convertY, onKeyValueChanged]);
10098
10584
  // Handle select all keys
10099
10585
  useEffect(() => {
10100
10586
  const observer = observables.onSelectAllKeys.add(() => {
@@ -10112,6 +10598,60 @@ const KeyPointComponent = (props) => {
10112
10598
  observables.onSelectAllKeys.remove(observer);
10113
10599
  };
10114
10600
  }, [observables, actions, curve, keyId]);
10601
+ // Track active key points count in a ref to avoid stale closure in handlePointerMove
10602
+ const activeKeyPointsRef = useRef(state.activeKeyPoints);
10603
+ activeKeyPointsRef.current = state.activeKeyPoints;
10604
+ // Multi-point movement: store offset from main key point
10605
+ const offsetToMain = useRef({ x: 0, y: 0 });
10606
+ const isMainKeyPoint = useRef(false);
10607
+ // When a main key point is set (multi-selection), store offset from it
10608
+ useEffect(() => {
10609
+ const observer = observables.onMainKeyPointSet.add((info) => {
10610
+ // Check if WE are the main key point
10611
+ if (info.curve === curve && info.keyId === keyId) {
10612
+ isMainKeyPoint.current = true;
10613
+ return;
10614
+ }
10615
+ isMainKeyPoint.current = false;
10616
+ // Store offset from the main key point position
10617
+ offsetToMain.current = {
10618
+ x: currentXRef.current - info.x,
10619
+ y: currentYRef.current - info.y,
10620
+ };
10621
+ });
10622
+ return () => {
10623
+ observables.onMainKeyPointSet.remove(observer);
10624
+ };
10625
+ }, [observables, curve, keyId]);
10626
+ // When the main key point moves, follow it with offset
10627
+ useEffect(() => {
10628
+ const observer = observables.onMainKeyPointMoved.add((pos) => {
10629
+ // Skip if we ARE the main key point
10630
+ if (isMainKeyPoint.current) {
10631
+ return;
10632
+ }
10633
+ if (selectedState === SelectionState.None) {
10634
+ return;
10635
+ }
10636
+ // Move frame for selected + siblings (but not first key)
10637
+ if (keyId !== 0) {
10638
+ const newX = pos.x + offsetToMain.current.x;
10639
+ currentXRef.current = newX;
10640
+ setCurrentX(newX);
10641
+ onFrameValueChanged(invertX(newX));
10642
+ }
10643
+ // Move value only for directly selected points
10644
+ if (selectedState === SelectionState.Selected) {
10645
+ const newY = pos.y + offsetToMain.current.y;
10646
+ currentYRef.current = newY;
10647
+ setCurrentY(newY);
10648
+ onKeyValueChanged(invertY(newY));
10649
+ }
10650
+ });
10651
+ return () => {
10652
+ observables.onMainKeyPointMoved.remove(observer);
10653
+ };
10654
+ }, [observables, curve, keyId, selectedState, invertX, invertY, onFrameValueChanged, onKeyValueChanged]);
10115
10655
  // Mouse/pointer handlers
10116
10656
  const handlePointerDown = useCallback((evt) => {
10117
10657
  const animationType = curve.animation.dataType;
@@ -10143,29 +10683,64 @@ const KeyPointComponent = (props) => {
10143
10683
  lockY.current = false;
10144
10684
  accumulatedX.current = 0;
10145
10685
  accumulatedY.current = 0;
10146
- // Handle selection
10686
+ // Handle selection (matches v1's _select logic)
10147
10687
  if (!evt.ctrlKey) {
10148
10688
  if (!isSelected()) {
10689
+ // Not in list, not multi-select: clear and add self
10149
10690
  actions.setActiveKeyPoints([{ curve, keyId }]);
10691
+ // Single selection → no mainKeyPoint (like v1)
10692
+ actions.setMainKeyPoint(null);
10693
+ }
10694
+ else {
10695
+ // Already in list, not multi-select:
10696
+ // If >1 selected, promote this to mainKeyPoint (DON'T clear others — v1 behavior)
10697
+ // If only 1, mainKeyPoint = null
10698
+ if (state.activeKeyPoints && state.activeKeyPoints.length > 1) {
10699
+ const info = { x: currentXRef.current, y: currentYRef.current, curve, keyId };
10700
+ actions.setMainKeyPoint({ curve, keyId });
10701
+ queueMicrotask(() => {
10702
+ observables.onMainKeyPointSet.notifyObservers(info);
10703
+ });
10704
+ }
10705
+ else {
10706
+ actions.setMainKeyPoint(null);
10707
+ }
10150
10708
  }
10151
10709
  }
10152
10710
  else {
10153
- actions.setActiveKeyPoints((prev) => {
10154
- const current = prev || [];
10155
- const matchesCurve = (kp) => kp.curve.animation.uniqueId === curve.animation.uniqueId && kp.curve.property === curve.property && kp.keyId === keyId;
10156
- const isCurrentlySelected = current.some(matchesCurve);
10157
- if (isCurrentlySelected) {
10711
+ // Ctrl-click: toggle selection
10712
+ if (isSelected()) {
10713
+ // Remove from list
10714
+ actions.setActiveKeyPoints((prev) => {
10715
+ const current = prev || [];
10716
+ const matchesCurve = (kp) => kp.curve.animation.uniqueId === curve.animation.uniqueId && kp.curve.property === curve.property && kp.keyId === keyId;
10158
10717
  return current.filter((kp) => !matchesCurve(kp));
10718
+ });
10719
+ actions.setMainKeyPoint(null);
10720
+ }
10721
+ else {
10722
+ // Add to list
10723
+ actions.setActiveKeyPoints((prev) => {
10724
+ const current = prev || [];
10725
+ return [...current, { curve, keyId }];
10726
+ });
10727
+ // Multi selection is now engaged
10728
+ if ((state.activeKeyPoints?.length ?? 0) + 1 > 1) {
10729
+ const info = { x: currentXRef.current, y: currentYRef.current, curve, keyId };
10730
+ actions.setMainKeyPoint({ curve, keyId });
10731
+ queueMicrotask(() => {
10732
+ observables.onMainKeyPointSet.notifyObservers(info);
10733
+ });
10159
10734
  }
10160
10735
  else {
10161
- return [...current, { curve, keyId }];
10736
+ actions.setMainKeyPoint(null);
10162
10737
  }
10163
- });
10738
+ }
10164
10739
  }
10165
10740
  observables.onActiveKeyPointChanged.notifyObservers();
10166
10741
  // Capture pointer for drag
10167
10742
  evt.target.setPointerCapture(evt.pointerId);
10168
- }, [curve, keyId, actions, observables, isSelected]);
10743
+ }, [curve, keyId, actions, observables, isSelected, state.activeKeyPoints]);
10169
10744
  const handlePointerMove = useCallback((evt) => {
10170
10745
  if (!pointerIsDown.current || selectedState !== SelectionState.Selected) {
10171
10746
  return;
@@ -10226,6 +10801,13 @@ const KeyPointComponent = (props) => {
10226
10801
  currentYRef.current = newY;
10227
10802
  setCurrentX(newX);
10228
10803
  setCurrentY(newY);
10804
+ // Notify other selected key points to follow (multi-point movement)
10805
+ const activeKeyPoints = activeKeyPointsRef.current;
10806
+ if (activeKeyPoints && activeKeyPoints.length > 1) {
10807
+ requestAnimationFrame(() => {
10808
+ observables.onMainKeyPointMoved.notifyObservers({ x: newX, y: newY });
10809
+ });
10810
+ }
10229
10811
  }
10230
10812
  else {
10231
10813
  // Tangent manipulation
@@ -10435,6 +11017,129 @@ const useStyles$t = makeStyles({
10435
11017
  pointerEvents: "none",
10436
11018
  },
10437
11019
  });
11020
+ /**
11021
+ * Evaluates active animations and produces CurveData instances with extracted key values.
11022
+ * @param activeAnimations - The currently active animations to evaluate
11023
+ * @param activeChannels - The currently active channels to determine which curves to show for multi-channel animations
11024
+ * @returns An array of CurveData instances representing the curves to display in the graph
11025
+ */
11026
+ function EvaluateKeys(activeAnimations, activeChannels) {
11027
+ const result = [];
11028
+ // Helper to set default tangents across all curves
11029
+ const setDefaultInTangent = (keyId) => {
11030
+ for (const curve of result) {
11031
+ curve.storeDefaultInTangent(keyId);
11032
+ }
11033
+ };
11034
+ const setDefaultOutTangent = (keyId) => {
11035
+ for (const curve of result) {
11036
+ curve.storeDefaultOutTangent(keyId);
11037
+ }
11038
+ };
11039
+ for (const animation of activeAnimations) {
11040
+ const keys = animation.getKeys();
11041
+ if (keys.length === 0) {
11042
+ continue;
11043
+ }
11044
+ const channelColor = activeChannels[animation.uniqueId];
11045
+ const curvesToAdd = [];
11046
+ // Create curves based on data type
11047
+ switch (animation.dataType) {
11048
+ case Animation.ANIMATIONTYPE_FLOAT:
11049
+ curvesToAdd.push(new CurveData(channelColor || DefaultCurveColor, animation));
11050
+ break;
11051
+ case Animation.ANIMATIONTYPE_VECTOR2:
11052
+ if (!channelColor || channelColor === ChannelColors.X) {
11053
+ curvesToAdd.push(new CurveData(ChannelColors.X, animation, "x", () => Vector2.Zero(), setDefaultInTangent, setDefaultOutTangent));
11054
+ }
11055
+ if (!channelColor || channelColor === ChannelColors.Y) {
11056
+ curvesToAdd.push(new CurveData(ChannelColors.Y, animation, "y", () => Vector2.Zero(), setDefaultInTangent, setDefaultOutTangent));
11057
+ }
11058
+ break;
11059
+ case Animation.ANIMATIONTYPE_VECTOR3:
11060
+ if (!channelColor || channelColor === ChannelColors.X) {
11061
+ curvesToAdd.push(new CurveData(ChannelColors.X, animation, "x", () => Vector3.Zero(), setDefaultInTangent, setDefaultOutTangent));
11062
+ }
11063
+ if (!channelColor || channelColor === ChannelColors.Y) {
11064
+ curvesToAdd.push(new CurveData(ChannelColors.Y, animation, "y", () => Vector3.Zero(), setDefaultInTangent, setDefaultOutTangent));
11065
+ }
11066
+ if (!channelColor || channelColor === ChannelColors.Z) {
11067
+ curvesToAdd.push(new CurveData(ChannelColors.Z, animation, "z", () => Vector3.Zero(), setDefaultInTangent, setDefaultOutTangent));
11068
+ }
11069
+ break;
11070
+ case Animation.ANIMATIONTYPE_COLOR3:
11071
+ if (!channelColor || channelColor === ColorChannelColors.R) {
11072
+ curvesToAdd.push(new CurveData(ColorChannelColors.R, animation, "r", () => Color3.Black(), setDefaultInTangent, setDefaultOutTangent));
11073
+ }
11074
+ if (!channelColor || channelColor === ColorChannelColors.G) {
11075
+ curvesToAdd.push(new CurveData(ColorChannelColors.G, animation, "g", () => Color3.Black(), setDefaultInTangent, setDefaultOutTangent));
11076
+ }
11077
+ if (!channelColor || channelColor === ColorChannelColors.B) {
11078
+ curvesToAdd.push(new CurveData(ColorChannelColors.B, animation, "b", () => Color3.Black(), setDefaultInTangent, setDefaultOutTangent));
11079
+ }
11080
+ break;
11081
+ case Animation.ANIMATIONTYPE_COLOR4:
11082
+ if (!channelColor || channelColor === ColorChannelColors.R) {
11083
+ curvesToAdd.push(new CurveData(ColorChannelColors.R, animation, "r", () => new Color4(), setDefaultInTangent, setDefaultOutTangent));
11084
+ }
11085
+ if (!channelColor || channelColor === ColorChannelColors.G) {
11086
+ curvesToAdd.push(new CurveData(ColorChannelColors.G, animation, "g", () => new Color4(), setDefaultInTangent, setDefaultOutTangent));
11087
+ }
11088
+ if (!channelColor || channelColor === ColorChannelColors.B) {
11089
+ curvesToAdd.push(new CurveData(ColorChannelColors.B, animation, "b", () => new Color4(), setDefaultInTangent, setDefaultOutTangent));
11090
+ }
11091
+ if (!channelColor || channelColor === ColorChannelColors.A) {
11092
+ curvesToAdd.push(new CurveData(ColorChannelColors.A, animation, "a", () => new Color4(), setDefaultInTangent, setDefaultOutTangent));
11093
+ }
11094
+ break;
11095
+ case Animation.ANIMATIONTYPE_QUATERNION:
11096
+ if (!channelColor || channelColor === ChannelColors.X) {
11097
+ curvesToAdd.push(new CurveData(ChannelColors.X, animation, "x", () => new Quaternion(), setDefaultInTangent, setDefaultOutTangent));
11098
+ }
11099
+ if (!channelColor || channelColor === ChannelColors.Y) {
11100
+ curvesToAdd.push(new CurveData(ChannelColors.Y, animation, "y", () => new Quaternion(), setDefaultInTangent, setDefaultOutTangent));
11101
+ }
11102
+ if (!channelColor || channelColor === ChannelColors.Z) {
11103
+ curvesToAdd.push(new CurveData(ChannelColors.Z, animation, "z", () => new Quaternion(), setDefaultInTangent, setDefaultOutTangent));
11104
+ }
11105
+ if (!channelColor || channelColor === ChannelColors.W) {
11106
+ curvesToAdd.push(new CurveData(ChannelColors.W, animation, "w", () => new Quaternion(), setDefaultInTangent, setDefaultOutTangent));
11107
+ }
11108
+ break;
11109
+ }
11110
+ ExtractValuesFromKeys(keys, curvesToAdd);
11111
+ // Wire up siblings so frame changes sync across all curves for the same animation
11112
+ for (const curve of curvesToAdd) {
11113
+ curve.siblings = curvesToAdd;
11114
+ }
11115
+ result.push(...curvesToAdd);
11116
+ }
11117
+ return result;
11118
+ }
11119
+ /**
11120
+ * Extracts key values, tangents, and interpolation data from animation keys into CurveData instances.
11121
+ * @param keys - The animation keys to extract data from
11122
+ * @param curves - CurveData to push changes to
11123
+ */
11124
+ function ExtractValuesFromKeys(keys, curves) {
11125
+ for (const key of keys) {
11126
+ const lockedTangent = key.lockedTangent ?? true;
11127
+ for (const curve of curves) {
11128
+ const prop = curve.property;
11129
+ const value = prop ? key.value[prop] : key.value;
11130
+ const inTangent = prop ? key.inTangent?.[prop] : key.inTangent;
11131
+ const outTangent = prop ? key.outTangent?.[prop] : key.outTangent;
11132
+ curve.keys.push({
11133
+ frame: key.frame,
11134
+ value,
11135
+ inTangent,
11136
+ outTangent,
11137
+ lockedTangent,
11138
+ interpolation: key.interpolation,
11139
+ });
11140
+ }
11141
+ }
11142
+ }
10438
11143
  /**
10439
11144
  * Main graph area for displaying and editing animation curves
10440
11145
  * @returns The graph component
@@ -10498,6 +11203,33 @@ const Graph = ({ width, height }) => {
10498
11203
  value: value,
10499
11204
  lockedTangent: true,
10500
11205
  };
11206
+ // Compute Hermite 1st-derivative tangents so the curve shape is preserved (like v1)
11207
+ if (leftKey?.outTangent !== undefined && rightKey?.inTangent !== undefined) {
11208
+ const invFrameDelta = 1.0 / (rightKey.frame - leftKey.frame);
11209
+ const cutTime = (currentFrame - leftKey.frame) * invFrameDelta;
11210
+ let derivative = null;
11211
+ switch (currentAnimation.dataType) {
11212
+ case Animation.ANIMATIONTYPE_FLOAT:
11213
+ derivative = Scalar.Hermite1stDerivative(leftKey.value * invFrameDelta, leftKey.outTangent, rightKey.value * invFrameDelta, rightKey.inTangent, cutTime);
11214
+ break;
11215
+ case Animation.ANIMATIONTYPE_VECTOR2:
11216
+ derivative = Vector2.Hermite1stDerivative(leftKey.value.scale(invFrameDelta), leftKey.outTangent, rightKey.value.scale(invFrameDelta), rightKey.inTangent, cutTime);
11217
+ break;
11218
+ case Animation.ANIMATIONTYPE_VECTOR3:
11219
+ derivative = Vector3.Hermite1stDerivative(leftKey.value.scale(invFrameDelta), leftKey.outTangent, rightKey.value.scale(invFrameDelta), rightKey.inTangent, cutTime);
11220
+ break;
11221
+ case Animation.ANIMATIONTYPE_COLOR3:
11222
+ derivative = Color3.Hermite1stDerivative(leftKey.value.scale(invFrameDelta), leftKey.outTangent, rightKey.value.scale(invFrameDelta), rightKey.inTangent, cutTime);
11223
+ break;
11224
+ case Animation.ANIMATIONTYPE_COLOR4:
11225
+ derivative = Color4.Hermite1stDerivative(leftKey.value.scale(invFrameDelta), leftKey.outTangent, rightKey.value.scale(invFrameDelta), rightKey.inTangent, cutTime);
11226
+ break;
11227
+ }
11228
+ if (derivative !== null) {
11229
+ newKey.inTangent = derivative;
11230
+ newKey.outTangent = derivative.clone ? derivative.clone() : derivative;
11231
+ }
11232
+ }
10501
11233
  keys.splice(indexToAdd + 1, 0, newKey);
10502
11234
  }
10503
11235
  currentAnimation.setKeys(keys);
@@ -10532,6 +11264,9 @@ const Graph = ({ width, height }) => {
10532
11264
  const keys = animation.getKeys();
10533
11265
  const sortedIndices = Array.from(keyIndices).sort((a, b) => b - a); // Sort descending
10534
11266
  for (const index of sortedIndices) {
11267
+ if (index === 0 || index === keys.length - 1) {
11268
+ continue; // Cannot delete first or last key
11269
+ }
10535
11270
  if (index >= 0 && index < keys.length) {
10536
11271
  keys.splice(index, 1);
10537
11272
  }
@@ -10544,119 +11279,31 @@ const Graph = ({ width, height }) => {
10544
11279
  observables.onActiveAnimationChanged.notifyObservers({});
10545
11280
  });
10546
11281
  // Note: Tangent operations (flatten, linear, break, unify, step) are handled by KeyPointComponent
10547
- // Each selected keypoint subscribes to the observables and handles its own tangent updates (like v1)
11282
+ // Each selected keypoint subscribes to the observables and handles its own tangent updates
10548
11283
  return () => {
10549
11284
  observables.onCreateOrUpdateKeyPointRequired.remove(onCreateOrUpdateKeyPointRequired);
10550
11285
  observables.onFrameRequired.remove(onFrameRequired);
10551
11286
  observables.onDeleteKeyActiveKeyPoints.remove(onDeleteKeyActiveKeyPoints);
10552
11287
  };
10553
11288
  }, [observables, state.activeAnimations, state.activeFrame, state.activeKeyPoints, actions]);
10554
- const curves = useMemo(() => {
10555
- const result = [];
10556
- // Helper to set default tangents across all curves (like v1)
10557
- const setDefaultInTangent = (keyId) => {
10558
- for (const curve of result) {
10559
- curve.storeDefaultInTangent(keyId);
10560
- }
11289
+ // Invalidation counter incremented when keys are added/deleted so curves recompute
11290
+ const [curveVersion, invalidateCurves] = useReducer((c) => c + 1, 0);
11291
+ useEffect(() => {
11292
+ const observer = observables.onActiveAnimationChanged.add(() => invalidateCurves());
11293
+ return () => {
11294
+ observables.onActiveAnimationChanged.remove(observer);
10561
11295
  };
10562
- const setDefaultOutTangent = (keyId) => {
10563
- for (const curve of result) {
10564
- curve.storeDefaultOutTangent(keyId);
10565
- }
11296
+ }, [observables]);
11297
+ const curves = useMemo(() => EvaluateKeys(state.activeAnimations, state.activeChannels), [state.activeAnimations, state.activeChannels, curveVersion]);
11298
+ // Re-render when any curve's key data is mutated (e.g. sibling frame sync)
11299
+ // so key point positions stay in sync with their curve paths
11300
+ const [, invalidateKeyPoints] = useReducer((c) => c + 1, 0);
11301
+ useEffect(() => {
11302
+ const observers = curves.map((curve) => curve.onDataUpdatedObservable.add(invalidateKeyPoints));
11303
+ return () => {
11304
+ curves.forEach((curve, i) => curve.onDataUpdatedObservable.remove(observers[i]));
10566
11305
  };
10567
- for (const animation of state.activeAnimations) {
10568
- const keys = animation.getKeys();
10569
- if (keys.length === 0) {
10570
- continue;
10571
- }
10572
- const channelColor = state.activeChannels[animation.uniqueId];
10573
- const curvesToAdd = [];
10574
- // Create curves based on data type (like v1's _evaluateKeys)
10575
- switch (animation.dataType) {
10576
- case Animation.ANIMATIONTYPE_FLOAT:
10577
- curvesToAdd.push(new CurveData(channelColor || DefaultCurveColor, animation));
10578
- break;
10579
- case Animation.ANIMATIONTYPE_VECTOR2:
10580
- if (!channelColor || channelColor === ChannelColors.X) {
10581
- curvesToAdd.push(new CurveData(ChannelColors.X, animation, "x", () => Vector2.Zero(), setDefaultInTangent, setDefaultOutTangent));
10582
- }
10583
- if (!channelColor || channelColor === ChannelColors.Y) {
10584
- curvesToAdd.push(new CurveData(ChannelColors.Y, animation, "y", () => Vector2.Zero(), setDefaultInTangent, setDefaultOutTangent));
10585
- }
10586
- break;
10587
- case Animation.ANIMATIONTYPE_VECTOR3:
10588
- if (!channelColor || channelColor === ChannelColors.X) {
10589
- curvesToAdd.push(new CurveData(ChannelColors.X, animation, "x", () => Vector3.Zero(), setDefaultInTangent, setDefaultOutTangent));
10590
- }
10591
- if (!channelColor || channelColor === ChannelColors.Y) {
10592
- curvesToAdd.push(new CurveData(ChannelColors.Y, animation, "y", () => Vector3.Zero(), setDefaultInTangent, setDefaultOutTangent));
10593
- }
10594
- if (!channelColor || channelColor === ChannelColors.Z) {
10595
- curvesToAdd.push(new CurveData(ChannelColors.Z, animation, "z", () => Vector3.Zero(), setDefaultInTangent, setDefaultOutTangent));
10596
- }
10597
- break;
10598
- case Animation.ANIMATIONTYPE_COLOR3:
10599
- if (!channelColor || channelColor === ColorChannelColors.R) {
10600
- curvesToAdd.push(new CurveData(ColorChannelColors.R, animation, "r", () => Color3.Black(), setDefaultInTangent, setDefaultOutTangent));
10601
- }
10602
- if (!channelColor || channelColor === ColorChannelColors.G) {
10603
- curvesToAdd.push(new CurveData(ColorChannelColors.G, animation, "g", () => Color3.Black(), setDefaultInTangent, setDefaultOutTangent));
10604
- }
10605
- if (!channelColor || channelColor === ColorChannelColors.B) {
10606
- curvesToAdd.push(new CurveData(ColorChannelColors.B, animation, "b", () => Color3.Black(), setDefaultInTangent, setDefaultOutTangent));
10607
- }
10608
- break;
10609
- case Animation.ANIMATIONTYPE_COLOR4:
10610
- if (!channelColor || channelColor === ColorChannelColors.R) {
10611
- curvesToAdd.push(new CurveData(ColorChannelColors.R, animation, "r", () => new Color4(), setDefaultInTangent, setDefaultOutTangent));
10612
- }
10613
- if (!channelColor || channelColor === ColorChannelColors.G) {
10614
- curvesToAdd.push(new CurveData(ColorChannelColors.G, animation, "g", () => new Color4(), setDefaultInTangent, setDefaultOutTangent));
10615
- }
10616
- if (!channelColor || channelColor === ColorChannelColors.B) {
10617
- curvesToAdd.push(new CurveData(ColorChannelColors.B, animation, "b", () => new Color4(), setDefaultInTangent, setDefaultOutTangent));
10618
- }
10619
- if (!channelColor || channelColor === ColorChannelColors.A) {
10620
- curvesToAdd.push(new CurveData(ColorChannelColors.A, animation, "a", () => new Color4(), setDefaultInTangent, setDefaultOutTangent));
10621
- }
10622
- break;
10623
- case Animation.ANIMATIONTYPE_QUATERNION:
10624
- if (!channelColor || channelColor === ChannelColors.X) {
10625
- curvesToAdd.push(new CurveData(ChannelColors.X, animation, "x", () => new Quaternion(), setDefaultInTangent, setDefaultOutTangent));
10626
- }
10627
- if (!channelColor || channelColor === ChannelColors.Y) {
10628
- curvesToAdd.push(new CurveData(ChannelColors.Y, animation, "y", () => new Quaternion(), setDefaultInTangent, setDefaultOutTangent));
10629
- }
10630
- if (!channelColor || channelColor === ChannelColors.Z) {
10631
- curvesToAdd.push(new CurveData(ChannelColors.Z, animation, "z", () => new Quaternion(), setDefaultInTangent, setDefaultOutTangent));
10632
- }
10633
- if (!channelColor || channelColor === ChannelColors.W) {
10634
- curvesToAdd.push(new CurveData(ChannelColors.W, animation, "w", () => new Quaternion(), setDefaultInTangent, setDefaultOutTangent));
10635
- }
10636
- break;
10637
- }
10638
- // Populate keys for each curve (like v1's _extractValuesFromKeys)
10639
- for (const key of keys) {
10640
- const lockedTangent = key.lockedTangent ?? true;
10641
- for (const curve of curvesToAdd) {
10642
- const prop = curve.property;
10643
- const value = prop ? key.value[prop] : key.value;
10644
- const inTangent = prop ? key.inTangent?.[prop] : key.inTangent;
10645
- const outTangent = prop ? key.outTangent?.[prop] : key.outTangent;
10646
- curve.keys.push({
10647
- frame: key.frame,
10648
- value,
10649
- inTangent,
10650
- outTangent,
10651
- lockedTangent,
10652
- interpolation: key.interpolation,
10653
- });
10654
- }
10655
- }
10656
- result.push(...curvesToAdd);
10657
- }
10658
- return result;
10659
- }, [state.activeAnimations, state.activeChannels]);
11306
+ }, [curves, invalidateKeyPoints]);
10660
11307
  // Calculate value range
10661
11308
  const valueRange = useMemo(() => {
10662
11309
  let minValue = 0;
@@ -11153,7 +11800,8 @@ const useStyles$q = makeStyles({
11153
11800
  backgroundColor: tokens.colorNeutralBackground2,
11154
11801
  overflow: "hidden",
11155
11802
  userSelect: "none",
11156
- pointerEvents: "none",
11803
+ cursor: "pointer",
11804
+ touchAction: "none",
11157
11805
  },
11158
11806
  svg: {
11159
11807
  width: "100%",
@@ -11186,10 +11834,11 @@ const OFFSET_X = 10;
11186
11834
  */
11187
11835
  const RangeFrameBar = ({ width }) => {
11188
11836
  const styles = useStyles$q();
11189
- const { state, observables } = useCurveEditor();
11837
+ const { state, actions, observables } = useCurveEditor();
11190
11838
  const svgRef = useRef(null);
11191
11839
  const [viewWidth, setViewWidth] = useState(width);
11192
11840
  const [displayFrame, setDisplayFrame] = useState(state.activeFrame);
11841
+ const pointerIsDown = useRef(false);
11193
11842
  // Re-render when range updates
11194
11843
  // useCallback stabilizes the accessor to prevent infinite re-render loops
11195
11844
  useObservableState(useCallback(() => ({}), []), observables.onRangeUpdated);
@@ -11215,6 +11864,26 @@ const RangeFrameBar = ({ width }) => {
11215
11864
  setDisplayFrame(state.activeFrame);
11216
11865
  }
11217
11866
  }, [state.activeFrame, state.isPlaying]);
11867
+ // Playhead scrubbing — linear interpolation from pointer position to frame
11868
+ const pointerToFrame = useCallback((offsetX) => {
11869
+ const { fromKey, toKey } = state;
11870
+ return Math.round(Math.max(fromKey, Math.min(toKey, (offsetX / viewWidth) * (toKey - fromKey) + fromKey)));
11871
+ }, [state.fromKey, state.toKey, viewWidth]);
11872
+ const handlePointerDown = useCallback((evt) => {
11873
+ pointerIsDown.current = true;
11874
+ evt.currentTarget.setPointerCapture(evt.pointerId);
11875
+ actions.moveToFrame(pointerToFrame(evt.nativeEvent.offsetX));
11876
+ }, [actions, pointerToFrame]);
11877
+ const handlePointerMove = useCallback((evt) => {
11878
+ if (!pointerIsDown.current) {
11879
+ return;
11880
+ }
11881
+ actions.moveToFrame(pointerToFrame(evt.nativeEvent.offsetX));
11882
+ }, [actions, pointerToFrame]);
11883
+ const handlePointerUp = useCallback((evt) => {
11884
+ pointerIsDown.current = false;
11885
+ evt.currentTarget.releasePointerCapture(evt.pointerId);
11886
+ }, []);
11218
11887
  // Compute frame ticks
11219
11888
  const frameTicks = useMemo(() => {
11220
11889
  if (state.activeAnimations.length === 0) {
@@ -11269,7 +11938,7 @@ const RangeFrameBar = ({ width }) => {
11269
11938
  return jsx("line", { className: styles.activeFrameLine, x1: x, y1: 0, x2: x, y2: 40 });
11270
11939
  }, [displayFrame, frameToX, styles.activeFrameLine]);
11271
11940
  const viewBox = `${ -10} 0 ${viewWidth + OFFSET_X * 4} 40`;
11272
- return (jsx("div", { className: styles.root, children: jsxs("svg", { ref: svgRef, className: styles.svg, viewBox: viewBox, children: [frameTicks.map((frame, i) => {
11941
+ return (jsx("div", { className: styles.root, onPointerDown: handlePointerDown, onPointerMove: handlePointerMove, onPointerUp: handlePointerUp, onPointerCancel: handlePointerUp, children: jsxs("svg", { ref: svgRef, className: styles.svg, viewBox: viewBox, children: [frameTicks.map((frame, i) => {
11273
11942
  const x = frameToX(frame);
11274
11943
  return (jsxs("g", { children: [jsx("line", { className: styles.tickLine, x1: x, y1: 22, x2: x, y2: 40 }), jsx("text", { className: styles.tickLabel, x: x, y: 14, children: Math.round(frame) })] }, `tick-${frame}-${i}`));
11275
11944
  }), renderKeyframes, renderActiveFrame] }) }));
@@ -11528,6 +12197,22 @@ const RangeSelector = () => {
11528
12197
  }, onPointerDown: handlePointerDown, onPointerMove: handlePointerMove, onPointerUp: handlePointerUp, onPointerCancel: handlePointerCancel, children: [jsx("div", { id: "left-handle", className: styles.handle, children: jsxs("div", { className: styles.handleIcon, children: [jsx("div", {}), jsx("div", {}), jsx("div", {})] }) }), jsx("div", { className: styles.label, children: Math.floor(state.fromKey) }), jsx("div", { className: styles.label, children: Math.floor(state.toKey) }), jsx("div", { id: "right-handle", className: styles.handle, children: jsxs("div", { className: styles.handleIcon, children: [jsx("div", {}), jsx("div", {}), jsx("div", {})] }) })] }) }));
11529
12198
  };
11530
12199
 
12200
+ /**
12201
+ * Checks whether any of the given animations has a key at the specified frame.
12202
+ * @param animations - The animations to check for keys
12203
+ * @param frame - The frame index to check for keys at
12204
+ * @returns True if a key exists at the frame, false otherwise.
12205
+ */
12206
+ function GetKeyAtAnyFrameIndex(animations, frame) {
12207
+ for (const animation of animations) {
12208
+ for (const key of animation.getKeys()) {
12209
+ if (Math.floor(frame - key.frame) === 0) {
12210
+ return true;
12211
+ }
12212
+ }
12213
+ }
12214
+ return false;
12215
+ }
11531
12216
  const useStyles$n = makeStyles({
11532
12217
  root: {
11533
12218
  display: "flex",
@@ -11621,21 +12306,34 @@ const BottomBar = () => {
11621
12306
  setClipLength(newLength);
11622
12307
  actions.setClipLength(newLength);
11623
12308
  actions.setReferenceMaxFrame(newLength);
12309
+ // Move playhead to new clip end
12310
+ observables.onMoveToFrameRequired.notifyObservers(newLength);
12311
+ // Create a key at the boundary if one doesn't exist
12312
+ if (!GetKeyAtAnyFrameIndex(state.activeAnimations, newLength)) {
12313
+ observables.onCreateOrUpdateKeyPointRequired.notifyObservers();
12314
+ }
11624
12315
  });
11625
12316
  const onClipLengthDecreased = observables.onClipLengthDecreased.add((newLength) => {
11626
12317
  setClipLength(newLength);
11627
12318
  actions.setClipLength(newLength);
11628
12319
  actions.setReferenceMaxFrame(newLength);
12320
+ // Move playhead to new clip end
12321
+ observables.onMoveToFrameRequired.notifyObservers(newLength);
12322
+ // Create a key at the boundary if one doesn't exist
12323
+ if (!GetKeyAtAnyFrameIndex(state.activeAnimations, newLength)) {
12324
+ observables.onCreateOrUpdateKeyPointRequired.notifyObservers();
12325
+ }
11629
12326
  // Clamp toKey to new clip length
11630
12327
  if (toKeyRef.current > newLength) {
11631
12328
  actions.setToKey(newLength);
11632
12329
  }
12330
+ observables.onRangeUpdated.notifyObservers();
11633
12331
  });
11634
12332
  return () => {
11635
12333
  observables.onClipLengthIncreased.remove(onClipLengthIncreased);
11636
12334
  observables.onClipLengthDecreased.remove(onClipLengthDecreased);
11637
12335
  };
11638
- }, [observables, actions]);
12336
+ }, [observables, actions, state.activeAnimations]);
11639
12337
  const handlePlayForward = useCallback(() => {
11640
12338
  actions.play(true);
11641
12339
  }, [actions]);
@@ -12119,7 +12817,7 @@ const SoundCommandProperties = (props) => {
12119
12817
  else {
12120
12818
  sound.play();
12121
12819
  }
12122
- } }), 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) => {
12123
12821
  sound.setVolume(value);
12124
12822
  } }), jsx(BoundProperty, { component: SwitchPropertyLine, label: "Loop", target: sound, propertyKey: "loop" })] }));
12125
12823
  };
@@ -12159,7 +12857,7 @@ const ArcRotateCameraTransformProperties = (props) => {
12159
12857
  const upperBetaLimit = useProperty(camera, "upperBetaLimit") ?? Math.PI;
12160
12858
  const lowerRadiusLimit = useProperty(camera, "lowerRadiusLimit");
12161
12859
  const upperRadiusLimit = useProperty(camera, "upperRadiusLimit");
12162
- 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 })] }));
12163
12861
  };
12164
12862
  const ArcRotateCameraControlProperties = (props) => {
12165
12863
  const { camera } = props;
@@ -12171,8 +12869,19 @@ const ArcRotateCameraCollisionProperties = (props) => {
12171
12869
  };
12172
12870
  const ArcRotateCameraLimitsProperties = (props) => {
12173
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");
12174
12883
  // TODO-Iv2: Update defaultValues
12175
- 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" })] }));
12176
12885
  };
12177
12886
  const ArcRotateCameraBehaviorsProperties = (props) => {
12178
12887
  const { camera } = props;
@@ -12189,7 +12898,7 @@ const GeospatialCameraTransformProperties = (props) => {
12189
12898
  const pitchMax = limits?.pitchMax ?? Math.PI / 2;
12190
12899
  const radiusMin = limits?.radiusMin ?? 0;
12191
12900
  const radiusMax = limits?.radiusMax ?? Infinity;
12192
- 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" })] }));
12193
12902
  };
12194
12903
  const GeospatialCameraCollisionProperties = (props) => {
12195
12904
  const { camera } = props;
@@ -12266,7 +12975,7 @@ const CameraGeneralProperties = (props) => {
12266
12975
  const { camera } = props;
12267
12976
  const [toDisplayAngle, fromDisplayAngle, useDegrees] = useAngleConverters();
12268
12977
  const mode = useProperty(camera, "mode");
12269
- 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 })] }) })] }));
12270
12979
  };
12271
12980
 
12272
12981
  const FollowCameraTransformProperties = (props) => {
@@ -12985,7 +13694,7 @@ const Gradient = (props) => {
12985
13694
  // Only use compact mode when there are numeric values (spinbuttons) taking up space
12986
13695
  const hasNumericValues = !(gradient.value1 instanceof Color3 || gradient.value1 instanceof Color4) ||
12987
13696
  (gradient.value2 !== undefined && !(gradient.value2 instanceof Color3 || gradient.value2 instanceof Color4));
12988
- 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 }) })] }));
12989
13698
  };
12990
13699
  const FactorGradientCast = Gradient;
12991
13700
  const Color3GradientCast = Gradient;
@@ -13856,7 +14565,7 @@ const PBRBaseMaterialDebugProperties = (props) => {
13856
14565
  const SkyMaterialProperties = (props) => {
13857
14566
  const { material } = props;
13858
14567
  const [toDisplayAngle, fromDisplayAngle, useDegrees] = useAngleConverters();
13859
- 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" })] }));
13860
14569
  };
13861
14570
 
13862
14571
  const StandardMaterialGeneralProperties = (props) => {
@@ -17026,7 +17735,7 @@ const SpriteGeneralProperties = (props) => {
17026
17735
  const SpriteTransformProperties = (props) => {
17027
17736
  const { sprite } = props;
17028
17737
  const [toDisplayAngle, fromDisplayAngle, useDegrees] = useAngleConverters();
17029
- 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")] }));
17030
17739
  };
17031
17740
  const SpriteAnimationProperties = (props) => {
17032
17741
  const { sprite } = props;
@@ -18620,7 +19329,7 @@ const Contrast = {
18620
19329
  const handleExposureChange = (_, data) => {
18621
19330
  setExposure(data.value);
18622
19331
  };
18623
- 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 })] })] }));
18624
19333
  },
18625
19334
  };
18626
19335
  },
@@ -18842,7 +19551,7 @@ const Paintbrush = {
18842
19551
  const handleWidthChange = (_, data) => {
18843
19552
  setWidth(data.value);
18844
19553
  };
18845
- 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 })] }) }));
18846
19555
  },
18847
19556
  };
18848
19557
  },
@@ -19087,7 +19796,7 @@ const TransformProperties = (props) => {
19087
19796
  const quatRotation = useQuaternionProperty(transform, "rotationQuaternion");
19088
19797
  const [useDegrees] = useSetting(UseDegreesSettingDescriptor);
19089
19798
  const [useEuler] = useSetting(UseEulerSettingDescriptor);
19090
- 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 })] }));
19091
19800
  };
19092
19801
 
19093
19802
  const TransformPropertiesServiceDefinition = {
@@ -19115,8 +19824,8 @@ const TransformPropertiesServiceDefinition = {
19115
19824
 
19116
19825
  const AnimationGroupExplorerServiceDefinition = {
19117
19826
  friendlyName: "Animation Group Explorer",
19118
- consumes: [SceneExplorerServiceIdentity, SceneContextIdentity],
19119
- factory: (sceneExplorerService, sceneContext) => {
19827
+ consumes: [SceneExplorerServiceIdentity, SceneContextIdentity, WatcherServiceIdentity],
19828
+ factory: (sceneExplorerService, sceneContext, watcherService) => {
19120
19829
  const scene = sceneContext.currentScene;
19121
19830
  if (!scene) {
19122
19831
  return undefined;
@@ -19129,11 +19838,7 @@ const AnimationGroupExplorerServiceDefinition = {
19129
19838
  getEntityDisplayInfo: (entity) => {
19130
19839
  const namedEntity = entity instanceof AnimationGroup ? entity : entity.animation;
19131
19840
  const onChangeObservable = new Observable();
19132
- const nameHookToken = InterceptProperty(namedEntity, "name", {
19133
- afterSet: () => {
19134
- onChangeObservable.notifyObservers();
19135
- },
19136
- });
19841
+ const nameHookToken = watcherService.watchProperty(namedEntity, "name", () => onChangeObservable.notifyObservers());
19137
19842
  return {
19138
19843
  get name() {
19139
19844
  return namedEntity.name;
@@ -19199,8 +19904,8 @@ const AnimationGroupExplorerServiceDefinition = {
19199
19904
 
19200
19905
  const AtmosphereExplorerServiceDefinition = {
19201
19906
  friendlyName: "Atmosphere Explorer",
19202
- consumes: [SceneExplorerServiceIdentity, SceneContextIdentity],
19203
- factory: (sceneExplorerService, sceneContext) => {
19907
+ consumes: [SceneExplorerServiceIdentity, SceneContextIdentity, WatcherServiceIdentity],
19908
+ factory: (sceneExplorerService, sceneContext, watcherService) => {
19204
19909
  const scene = sceneContext.currentScene;
19205
19910
  if (!scene) {
19206
19911
  return undefined;
@@ -19211,11 +19916,7 @@ const AtmosphereExplorerServiceDefinition = {
19211
19916
  getRootEntities: () => (scene.getExternalData("atmosphere") ? [scene.getExternalData("atmosphere")] : []),
19212
19917
  getEntityDisplayInfo: (atmosphere) => {
19213
19918
  const onChangeObservable = new Observable();
19214
- const nameHookToken = InterceptProperty(atmosphere, "name", {
19215
- afterSet: () => {
19216
- onChangeObservable.notifyObservers();
19217
- },
19218
- });
19919
+ const nameHookToken = watcherService.watchProperty(atmosphere, "name", () => onChangeObservable.notifyObservers());
19219
19920
  return {
19220
19921
  get name() {
19221
19922
  return atmosphere.name;
@@ -19274,8 +19975,8 @@ const DisposableCommandServiceDefinition = {
19274
19975
 
19275
19976
  const EffectLayerExplorerServiceDefinition = {
19276
19977
  friendlyName: "Effect Layer Explorer",
19277
- consumes: [SceneExplorerServiceIdentity, SceneContextIdentity],
19278
- factory: (sceneExplorerService, sceneContext) => {
19978
+ consumes: [SceneExplorerServiceIdentity, SceneContextIdentity, WatcherServiceIdentity],
19979
+ factory: (sceneExplorerService, sceneContext, watcherService) => {
19279
19980
  const scene = sceneContext.currentScene;
19280
19981
  if (!scene) {
19281
19982
  return undefined;
@@ -19286,11 +19987,7 @@ const EffectLayerExplorerServiceDefinition = {
19286
19987
  getRootEntities: () => scene.effectLayers,
19287
19988
  getEntityDisplayInfo: (effectLayer) => {
19288
19989
  const onChangeObservable = new Observable();
19289
- const nameHookToken = InterceptProperty(effectLayer, "name", {
19290
- afterSet: () => {
19291
- onChangeObservable.notifyObservers();
19292
- },
19293
- });
19990
+ const nameHookToken = watcherService.watchProperty(effectLayer, "name", () => onChangeObservable.notifyObservers());
19294
19991
  return {
19295
19992
  get name() {
19296
19993
  return effectLayer.name;
@@ -19316,8 +20013,8 @@ const EffectLayerExplorerServiceDefinition = {
19316
20013
 
19317
20014
  const FrameGraphExplorerServiceDefinition = {
19318
20015
  friendlyName: "Frame Graph Explorer",
19319
- consumes: [SceneExplorerServiceIdentity, SceneContextIdentity],
19320
- factory: (sceneExplorerService, sceneContext) => {
20016
+ consumes: [SceneExplorerServiceIdentity, SceneContextIdentity, WatcherServiceIdentity],
20017
+ factory: (sceneExplorerService, sceneContext, watcherService) => {
19321
20018
  const scene = sceneContext.currentScene;
19322
20019
  if (!scene) {
19323
20020
  return undefined;
@@ -19328,11 +20025,7 @@ const FrameGraphExplorerServiceDefinition = {
19328
20025
  getRootEntities: () => scene.frameGraphs,
19329
20026
  getEntityDisplayInfo: (frameGraph) => {
19330
20027
  const onChangeObservable = new Observable();
19331
- const nameHookToken = InterceptProperty(frameGraph, "name", {
19332
- afterSet: () => {
19333
- onChangeObservable.notifyObservers();
19334
- },
19335
- });
20028
+ const nameHookToken = watcherService.watchProperty(frameGraph, "name", () => onChangeObservable.notifyObservers());
19336
20029
  return {
19337
20030
  get name() {
19338
20031
  return frameGraph.name;
@@ -19353,9 +20046,7 @@ const FrameGraphExplorerServiceDefinition = {
19353
20046
  order: 900 /* DefaultCommandsOrder.FrameGraphPlay */,
19354
20047
  getCommand: (frameGraph) => {
19355
20048
  const onChangeObservable = new Observable();
19356
- const frameGraphHook = InterceptProperty(scene, "frameGraph", {
19357
- afterSet: () => onChangeObservable.notifyObservers(),
19358
- });
20049
+ const frameGraphHook = watcherService.watchProperty(scene, "frameGraph", () => onChangeObservable.notifyObservers());
19359
20050
  return {
19360
20051
  type: "toggle",
19361
20052
  displayName: "Make Active",
@@ -19418,8 +20109,8 @@ function IsControl(entity) {
19418
20109
  }
19419
20110
  const GuiExplorerServiceDefinition = {
19420
20111
  friendlyName: "GUI Explorer",
19421
- consumes: [SceneExplorerServiceIdentity, SceneContextIdentity],
19422
- factory: (sceneExplorerService, sceneContext) => {
20112
+ consumes: [SceneExplorerServiceIdentity, SceneContextIdentity, WatcherServiceIdentity],
20113
+ factory: (sceneExplorerService, sceneContext, watcherService) => {
19423
20114
  const scene = sceneContext.currentScene;
19424
20115
  if (!scene) {
19425
20116
  return undefined;
@@ -19445,10 +20136,8 @@ const GuiExplorerServiceDefinition = {
19445
20136
  const disposeActions = [];
19446
20137
  const onChangeObservable = new Observable();
19447
20138
  disposeActions.push(() => onChangeObservable.clear());
19448
- const nameHookToken = InterceptProperty(entity, "name", {
19449
- afterSet: () => {
19450
- onChangeObservable.notifyObservers();
19451
- },
20139
+ const nameHookToken = watcherService.watchProperty(entity, "name", () => {
20140
+ onChangeObservable.notifyObservers();
19452
20141
  });
19453
20142
  disposeActions.push(() => nameHookToken.dispose());
19454
20143
  if (!IsAdvancedDynamicTexture(entity) && IsContainer(entity)) {
@@ -19505,9 +20194,7 @@ const GuiExplorerServiceDefinition = {
19505
20194
  order: 1000 /* DefaultCommandsOrder.GuiHighlight */,
19506
20195
  getCommand: (control) => {
19507
20196
  const onChangeObservable = new Observable();
19508
- const showBoundingBoxHook = InterceptProperty(control, "isHighlighted", {
19509
- afterSet: () => onChangeObservable.notifyObservers(),
19510
- });
20197
+ const showBoundingBoxHook = watcherService.watchProperty(control, "isHighlighted", () => onChangeObservable.notifyObservers());
19511
20198
  return {
19512
20199
  type: "toggle",
19513
20200
  get displayName() {
@@ -19563,8 +20250,8 @@ const GuiExplorerServiceDefinition = {
19563
20250
 
19564
20251
  const MaterialExplorerServiceDefinition = {
19565
20252
  friendlyName: "Material Explorer",
19566
- consumes: [SceneExplorerServiceIdentity, SceneContextIdentity],
19567
- factory: (sceneExplorerService, sceneContext) => {
20253
+ consumes: [SceneExplorerServiceIdentity, SceneContextIdentity, WatcherServiceIdentity],
20254
+ factory: (sceneExplorerService, sceneContext, watcherService) => {
19568
20255
  const scene = sceneContext.currentScene;
19569
20256
  if (!scene) {
19570
20257
  return undefined;
@@ -19575,11 +20262,7 @@ const MaterialExplorerServiceDefinition = {
19575
20262
  getRootEntities: () => [...scene.materials, ...scene.multiMaterials],
19576
20263
  getEntityDisplayInfo: (material) => {
19577
20264
  const onChangeObservable = new Observable();
19578
- const nameHookToken = InterceptProperty(material, "name", {
19579
- afterSet: () => {
19580
- onChangeObservable.notifyObservers();
19581
- },
19582
- });
20265
+ const nameHookToken = watcherService.watchProperty(material, "name", () => onChangeObservable.notifyObservers());
19583
20266
  return {
19584
20267
  get name() {
19585
20268
  return material.name;
@@ -19619,8 +20302,8 @@ const MaterialExplorerServiceDefinition = {
19619
20302
 
19620
20303
  const NodeExplorerServiceDefinition = {
19621
20304
  friendlyName: "Node Explorer",
19622
- consumes: [SceneExplorerServiceIdentity, SceneContextIdentity, GizmoServiceIdentity],
19623
- factory: (sceneExplorerService, sceneContext, gizmoService) => {
20305
+ consumes: [SceneExplorerServiceIdentity, SceneContextIdentity, GizmoServiceIdentity, WatcherServiceIdentity],
20306
+ factory: (sceneExplorerService, sceneContext, gizmoService, watcherService) => {
19624
20307
  const scene = sceneContext.currentScene;
19625
20308
  if (!scene) {
19626
20309
  return undefined;
@@ -19659,14 +20342,8 @@ const NodeExplorerServiceDefinition = {
19659
20342
  getEntityChildren: (node) => node.getChildren(),
19660
20343
  getEntityDisplayInfo: (node) => {
19661
20344
  const onChangeObservable = new Observable();
19662
- const nameHookToken = InterceptProperty(node, "name", {
19663
- afterSet: () => onChangeObservable.notifyObservers(),
19664
- });
19665
- const parentHookToken = InterceptProperty(node, "parent", {
19666
- afterSet: () => {
19667
- nodeMovedObservable.notifyObservers(node);
19668
- },
19669
- });
20345
+ const nameHookToken = watcherService.watchProperty(node, "name", () => onChangeObservable.notifyObservers());
20346
+ const parentHookToken = watcherService.watchProperty(node, "parent", () => nodeMovedObservable.notifyObservers(node));
19670
20347
  return {
19671
20348
  get name() {
19672
20349
  return node.name;
@@ -19729,9 +20406,7 @@ const NodeExplorerServiceDefinition = {
19729
20406
  order: 1000 /* DefaultCommandsOrder.MeshBoundingBox */,
19730
20407
  getCommand: (mesh) => {
19731
20408
  const onChangeObservable = new Observable();
19732
- const showBoundingBoxHook = InterceptProperty(mesh, "showBoundingBox", {
19733
- afterSet: () => onChangeObservable.notifyObservers(),
19734
- });
20409
+ const showBoundingBoxHook = watcherService.watchProperty(mesh, "showBoundingBox", () => onChangeObservable.notifyObservers());
19735
20410
  return {
19736
20411
  type: "toggle",
19737
20412
  get displayName() {
@@ -19757,9 +20432,7 @@ const NodeExplorerServiceDefinition = {
19757
20432
  order: 1100 /* DefaultCommandsOrder.MeshVisibility */,
19758
20433
  getCommand: (mesh) => {
19759
20434
  const onChangeObservable = new Observable();
19760
- const isVisibleHook = InterceptProperty(mesh, "isVisible", {
19761
- afterSet: () => onChangeObservable.notifyObservers(),
19762
- });
20435
+ const isVisibleHook = watcherService.watchProperty(mesh, "isVisible", () => onChangeObservable.notifyObservers());
19763
20436
  return {
19764
20437
  type: "toggle",
19765
20438
  get displayName() {
@@ -19911,8 +20584,8 @@ const NodeExplorerServiceDefinition = {
19911
20584
 
19912
20585
  const ParticleSystemExplorerServiceDefinition = {
19913
20586
  friendlyName: "Particle System Explorer",
19914
- consumes: [SceneExplorerServiceIdentity, SceneContextIdentity],
19915
- factory: (sceneExplorerService, sceneContext) => {
20587
+ consumes: [SceneExplorerServiceIdentity, SceneContextIdentity, WatcherServiceIdentity],
20588
+ factory: (sceneExplorerService, sceneContext, watcherService) => {
19916
20589
  const scene = sceneContext.currentScene;
19917
20590
  if (!scene) {
19918
20591
  return undefined;
@@ -19923,11 +20596,7 @@ const ParticleSystemExplorerServiceDefinition = {
19923
20596
  getRootEntities: () => scene.particleSystems,
19924
20597
  getEntityDisplayInfo: (particleSystem) => {
19925
20598
  const onChangeObservable = new Observable();
19926
- const nameHookToken = InterceptProperty(particleSystem, "name", {
19927
- afterSet: () => {
19928
- onChangeObservable.notifyObservers();
19929
- },
19930
- });
20599
+ const nameHookToken = watcherService.watchProperty(particleSystem, "name", () => onChangeObservable.notifyObservers());
19931
20600
  return {
19932
20601
  get name() {
19933
20602
  return particleSystem.name;
@@ -19967,8 +20636,8 @@ const ParticleSystemExplorerServiceDefinition = {
19967
20636
 
19968
20637
  const PostProcessExplorerServiceDefinition = {
19969
20638
  friendlyName: "Post Process Explorer",
19970
- consumes: [SceneExplorerServiceIdentity, SceneContextIdentity],
19971
- factory: (sceneExplorerService, sceneContext) => {
20639
+ consumes: [SceneExplorerServiceIdentity, SceneContextIdentity, WatcherServiceIdentity],
20640
+ factory: (sceneExplorerService, sceneContext, watcherService) => {
19972
20641
  const scene = sceneContext.currentScene;
19973
20642
  if (!scene) {
19974
20643
  return undefined;
@@ -19979,11 +20648,7 @@ const PostProcessExplorerServiceDefinition = {
19979
20648
  getRootEntities: () => scene.postProcesses,
19980
20649
  getEntityDisplayInfo: (postProcess) => {
19981
20650
  const onChangeObservable = new Observable();
19982
- const nameHookToken = InterceptProperty(postProcess, "name", {
19983
- afterSet: () => {
19984
- onChangeObservable.notifyObservers();
19985
- },
19986
- });
20651
+ const nameHookToken = watcherService.watchProperty(postProcess, "name", () => onChangeObservable.notifyObservers());
19987
20652
  return {
19988
20653
  get name() {
19989
20654
  return postProcess.name;
@@ -20041,8 +20706,8 @@ const RenderingPipelineExplorerServiceDefinition = {
20041
20706
 
20042
20707
  const SkeletonExplorerServiceDefinition = {
20043
20708
  friendlyName: "Skeleton Explorer",
20044
- consumes: [SceneExplorerServiceIdentity, SceneContextIdentity],
20045
- factory: (sceneExplorerService, sceneContext) => {
20709
+ consumes: [SceneExplorerServiceIdentity, SceneContextIdentity, WatcherServiceIdentity],
20710
+ factory: (sceneExplorerService, sceneContext, watcherService) => {
20046
20711
  const scene = sceneContext.currentScene;
20047
20712
  if (!scene) {
20048
20713
  return undefined;
@@ -20055,16 +20720,8 @@ const SkeletonExplorerServiceDefinition = {
20055
20720
  getEntityChildren: (skeletonOrBone) => skeletonOrBone.getChildren(),
20056
20721
  getEntityDisplayInfo: (skeletonOrBone) => {
20057
20722
  const onChangeObservable = new Observable();
20058
- const nameHookToken = InterceptProperty(skeletonOrBone, "name", {
20059
- afterSet: () => onChangeObservable.notifyObservers(),
20060
- });
20061
- const parentHookToken = skeletonOrBone instanceof Skeleton
20062
- ? null
20063
- : InterceptProperty(skeletonOrBone, "parent", {
20064
- afterSet: () => {
20065
- boneMovedObservable.notifyObservers(skeletonOrBone);
20066
- },
20067
- });
20723
+ const nameHookToken = watcherService.watchProperty(skeletonOrBone, "name", () => onChangeObservable.notifyObservers());
20724
+ const parentHookToken = skeletonOrBone instanceof Skeleton ? null : watcherService.watchProperty(skeletonOrBone, "parent", () => boneMovedObservable.notifyObservers(skeletonOrBone));
20068
20725
  return {
20069
20726
  get name() {
20070
20727
  return skeletonOrBone.name;
@@ -20092,8 +20749,8 @@ const SkeletonExplorerServiceDefinition = {
20092
20749
 
20093
20750
  const SoundExplorerServiceDefinition = {
20094
20751
  friendlyName: "Sound Explorer",
20095
- consumes: [SceneExplorerServiceIdentity, SceneContextIdentity],
20096
- factory: (sceneExplorerService, sceneContext) => {
20752
+ consumes: [SceneExplorerServiceIdentity, SceneContextIdentity, WatcherServiceIdentity],
20753
+ factory: (sceneExplorerService, sceneContext, watcherService) => {
20097
20754
  const scene = sceneContext.currentScene;
20098
20755
  if (!scene) {
20099
20756
  return undefined;
@@ -20119,25 +20776,14 @@ const SoundExplorerServiceDefinition = {
20119
20776
  // If _mainSoundTrack is already defined, set up hooks immediately.
20120
20777
  hookMainSoundTrack(scene.mainSoundTrack);
20121
20778
  // Watch for _mainSoundTrack being set (it is lazily created by the mainSoundTrack getter in audioSceneComponent.ts).
20122
- const mainSoundTrackHook = InterceptProperty(scene, "_mainSoundTrack", {
20123
- afterSet: () => hookMainSoundTrack(scene._mainSoundTrack),
20124
- });
20779
+ const mainSoundTrackHook = watcherService.watchProperty(scene, "_mainSoundTrack", () => hookMainSoundTrack(scene._mainSoundTrack));
20125
20780
  const sectionRegistration = sceneExplorerService.addSection({
20126
20781
  displayName: "Sounds",
20127
20782
  order: 1400 /* DefaultSectionsOrder.Sounds */,
20128
20783
  getRootEntities: () => scene.mainSoundTrack?.soundCollection ?? [],
20129
20784
  getEntityDisplayInfo: (sound) => {
20130
20785
  const onChangeObservable = new Observable();
20131
- const displayNameHookToken = InterceptProperty(sound, "name", {
20132
- afterSet: () => {
20133
- onChangeObservable.notifyObservers();
20134
- },
20135
- });
20136
- const nameHookToken = InterceptProperty(sound, "name", {
20137
- afterSet: () => {
20138
- onChangeObservable.notifyObservers();
20139
- },
20140
- });
20786
+ const nameHookToken = watcherService.watchProperty(sound, "name", () => onChangeObservable.notifyObservers());
20141
20787
  return {
20142
20788
  get name() {
20143
20789
  return sound.name;
@@ -20145,7 +20791,6 @@ const SoundExplorerServiceDefinition = {
20145
20791
  onChange: onChangeObservable,
20146
20792
  dispose: () => {
20147
20793
  nameHookToken.dispose();
20148
- displayNameHookToken.dispose();
20149
20794
  onChangeObservable.clear();
20150
20795
  },
20151
20796
  };
@@ -20169,8 +20814,8 @@ const SoundExplorerServiceDefinition = {
20169
20814
 
20170
20815
  const SpriteManagerExplorerServiceDefinition = {
20171
20816
  friendlyName: "Sprite Manager Explorer",
20172
- consumes: [SceneExplorerServiceIdentity, SceneContextIdentity],
20173
- factory: (sceneExplorerService, sceneContext) => {
20817
+ consumes: [SceneExplorerServiceIdentity, SceneContextIdentity, WatcherServiceIdentity],
20818
+ factory: (sceneExplorerService, sceneContext, watcherService) => {
20174
20819
  const scene = sceneContext.currentScene;
20175
20820
  if (!scene) {
20176
20821
  return undefined;
@@ -20182,9 +20827,7 @@ const SpriteManagerExplorerServiceDefinition = {
20182
20827
  getEntityChildren: (spriteEntity) => (spriteEntity instanceof Sprite ? [] : spriteEntity.sprites),
20183
20828
  getEntityDisplayInfo: (spriteEntity) => {
20184
20829
  const onChangeObservable = new Observable();
20185
- const nameHookToken = InterceptProperty(spriteEntity, "name", {
20186
- afterSet: () => onChangeObservable.notifyObservers(),
20187
- });
20830
+ const nameHookToken = watcherService.watchProperty(spriteEntity, "name", () => onChangeObservable.notifyObservers());
20188
20831
  return {
20189
20832
  get name() {
20190
20833
  return spriteEntity.name;
@@ -20242,8 +20885,8 @@ const SpriteManagerExplorerServiceDefinition = {
20242
20885
 
20243
20886
  const TextureExplorerServiceDefinition = {
20244
20887
  friendlyName: "Texture Explorer",
20245
- consumes: [SceneExplorerServiceIdentity, SceneContextIdentity],
20246
- factory: (sceneExplorerService, sceneContext) => {
20888
+ consumes: [SceneExplorerServiceIdentity, SceneContextIdentity, WatcherServiceIdentity],
20889
+ factory: (sceneExplorerService, sceneContext, watcherService) => {
20247
20890
  const scene = sceneContext.currentScene;
20248
20891
  if (!scene) {
20249
20892
  return undefined;
@@ -20254,16 +20897,8 @@ const TextureExplorerServiceDefinition = {
20254
20897
  getRootEntities: () => scene.textures.filter((texture) => texture.getClassName() !== "AdvancedDynamicTexture"),
20255
20898
  getEntityDisplayInfo: (texture) => {
20256
20899
  const onChangeObservable = new Observable();
20257
- const displayNameHookToken = InterceptProperty(texture, "displayName", {
20258
- afterSet: () => {
20259
- onChangeObservable.notifyObservers();
20260
- },
20261
- });
20262
- const nameHookToken = InterceptProperty(texture, "name", {
20263
- afterSet: () => {
20264
- onChangeObservable.notifyObservers();
20265
- },
20266
- });
20900
+ const displayNameHookToken = watcherService.watchProperty(texture, "displayName", () => onChangeObservable.notifyObservers());
20901
+ const nameHookToken = watcherService.watchProperty(texture, "name", () => onChangeObservable.notifyObservers());
20267
20902
  return {
20268
20903
  get name() {
20269
20904
  return texture.displayName || texture.name || `${texture.getClassName() || "Unnamed Texture"} (${texture.uniqueId})`;
@@ -20813,97 +21448,6 @@ const GLTFValidationServiceDefinition = {
20813
21448
  },
20814
21449
  };
20815
21450
 
20816
- const HighlightSelectedEntitySettingDescriptor = {
20817
- key: "HighlightSelectedEntity",
20818
- defaultValue: true,
20819
- };
20820
- const HighlightServiceDefinition = {
20821
- friendlyName: "Highlight Service",
20822
- consumes: [SelectionServiceIdentity, SceneContextIdentity, SettingsStoreIdentity, ThemeServiceIdentity, GizmoServiceIdentity],
20823
- factory: (selectionService, sceneContext, settingsStore, themeService, gizmoService) => {
20824
- let outlineLayer = null;
20825
- let utilityLayer = null;
20826
- let currentScene = null;
20827
- let activeCameraObserver = null;
20828
- function disposeOutlineLayer() {
20829
- outlineLayer?.dispose();
20830
- outlineLayer = null;
20831
- utilityLayer?.dispose();
20832
- utilityLayer = null;
20833
- currentScene = null;
20834
- }
20835
- function getOrCreateOutlineLayer(scene) {
20836
- if (!outlineLayer || currentScene !== scene) {
20837
- disposeOutlineLayer();
20838
- utilityLayer = gizmoService.getUtilityLayer(scene);
20839
- outlineLayer = new SelectionOutlineLayer("InspectorSelectionOutline", utilityLayer.value.utilityLayerScene);
20840
- updateColor(outlineLayer);
20841
- currentScene = scene;
20842
- }
20843
- return outlineLayer;
20844
- }
20845
- function updateColor(outlineLayer) {
20846
- outlineLayer.outlineColor = Color3.FromHexString(themeService.theme.colorBrandForeground1);
20847
- }
20848
- function updateHighlight() {
20849
- const scene = sceneContext.currentScene;
20850
- const entity = selectionService.selectedEntity instanceof AbstractMesh && !(selectionService.selectedEntity instanceof GaussianSplattingMesh)
20851
- ? selectionService.selectedEntity
20852
- : null;
20853
- if (!entity || !settingsStore.readSetting(HighlightSelectedEntitySettingDescriptor) || !scene || !scene.activeCamera) {
20854
- disposeOutlineLayer();
20855
- return;
20856
- }
20857
- const layer = getOrCreateOutlineLayer(scene);
20858
- layer.clearSelection();
20859
- layer.addSelection(entity);
20860
- }
20861
- function watchActiveCamera(scene) {
20862
- activeCameraObserver?.remove();
20863
- activeCameraObserver = null;
20864
- if (scene) {
20865
- activeCameraObserver = scene.onActiveCameraChanged.add(updateHighlight);
20866
- }
20867
- }
20868
- // React to theme changes.
20869
- const themeObserver = themeService.onChanged.add(() => {
20870
- if (outlineLayer) {
20871
- updateColor(outlineLayer);
20872
- }
20873
- });
20874
- // React to selection changes.
20875
- const selectionObserver = selectionService.onSelectedEntityChanged.add(updateHighlight);
20876
- // React to scene changes.
20877
- const sceneObserver = sceneContext.currentSceneObservable.add(() => {
20878
- // Dispose the old layer when the scene changes.
20879
- disposeOutlineLayer();
20880
- watchActiveCamera(sceneContext.currentScene);
20881
- updateHighlight();
20882
- });
20883
- // React to setting changes.
20884
- const settingObserver = settingsStore.onChanged.add((setting) => {
20885
- if (setting === HighlightSelectedEntitySettingDescriptor.key) {
20886
- updateHighlight();
20887
- }
20888
- });
20889
- // Watch active camera on the initial scene.
20890
- watchActiveCamera(sceneContext.currentScene);
20891
- // Initial update.
20892
- updateHighlight();
20893
- return {
20894
- dispose: () => {
20895
- themeObserver.remove();
20896
- selectionObserver.remove();
20897
- sceneObserver.remove();
20898
- settingObserver.remove();
20899
- activeCameraObserver?.remove();
20900
- activeCameraObserver = null;
20901
- disposeOutlineLayer();
20902
- },
20903
- };
20904
- },
20905
- };
20906
-
20907
21451
  const PickingToolbar = (props) => {
20908
21452
  const { scene, selectEntity, gizmoService, ignoreBackfaces, highlightSelectedEntity, onHighlightSelectedEntityChange } = props;
20909
21453
  const meshDataCache = useMemo(() => new WeakMap(), [scene]);
@@ -21012,7 +21556,7 @@ const PickingServiceDefinition = {
21012
21556
  key: "Picking Service",
21013
21557
  verticalLocation: "top",
21014
21558
  horizontalLocation: "left",
21015
- suppressTeachingMoment: true,
21559
+ teachingMoment: false,
21016
21560
  component: () => {
21017
21561
  const scene = useObservableState(() => sceneContext.currentScene, sceneContext.currentSceneObservable);
21018
21562
  const selectEntity = useCallback((entity) => (selectionService.selectedEntity = entity), []);
@@ -21068,7 +21612,11 @@ const UserFeedbackServiceDefinition = {
21068
21612
  key: "User Feedback",
21069
21613
  verticalLocation: "bottom",
21070
21614
  horizontalLocation: "right",
21071
- 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
+ },
21072
21620
  component: () => {
21073
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") }) }));
21074
21622
  },
@@ -21259,7 +21807,9 @@ function ShowInspector(scene, options = {}) {
21259
21807
  // Tools pane tab and related services.
21260
21808
  ToolsServiceDefinition, ExportServiceDefinition, GLTFAnimationImportServiceDefinition, GLTFLoaderOptionsServiceDefinition, GLTFValidationServiceDefinition, CaptureToolsDefinition,
21261
21809
  // Settings pane tab and related services.
21262
- SettingsServiceDefinition, ShellSettingsServiceDefinition,
21810
+ SettingsServiceDefinition, WatcherSettingsServiceDefinition, ShellSettingsServiceDefinition,
21811
+ // Adds a button to refresh all properties manually (when watcher is in "manual" mode).
21812
+ WatcherRefreshToolbarServiceDefinition,
21263
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.).
21264
21814
  SelectionServiceDefinition,
21265
21815
  // Gizmos for manipulating objects in the scene.
@@ -21493,19 +22043,15 @@ function ConvertOptions(v1Options) {
21493
22043
  const { additionalNodes } = v1Options;
21494
22044
  const additionalNodesServiceDefinition = {
21495
22045
  friendlyName: "Additional Nodes (Backward Compatibility)",
21496
- consumes: [SceneExplorerServiceIdentity],
21497
- factory: (sceneExplorerService) => {
22046
+ consumes: [SceneExplorerServiceIdentity, WatcherServiceIdentity],
22047
+ factory: (sceneExplorerService, watcherService) => {
21498
22048
  const sceneExplorerSectionRegistrations = additionalNodes.map((node) => sceneExplorerService.addSection({
21499
22049
  displayName: node.name,
21500
22050
  order: Number.MAX_SAFE_INTEGER,
21501
22051
  getRootEntities: () => node.getContent(),
21502
22052
  getEntityDisplayInfo: (entity) => {
21503
22053
  const onChangeObservable = new Observable();
21504
- const nameHookToken = InterceptProperty(entity, "name", {
21505
- afterSet: () => {
21506
- onChangeObservable.notifyObservers();
21507
- },
21508
- });
22054
+ const nameHookToken = watcherService.watchProperty(entity, "name", () => onChangeObservable.notifyObservers());
21509
22055
  return {
21510
22056
  get name() {
21511
22057
  return entity.name;
@@ -21537,7 +22083,7 @@ function ConvertOptions(v1Options) {
21537
22083
  consumes: [SceneExplorerServiceIdentity],
21538
22084
  factory: (sceneExplorerService) => {
21539
22085
  const sceneExplorerCommandRegistrations = explorerExtensibility.flatMap((command) => command.entries.map((entry) => sceneExplorerService.addEntityCommand({
21540
- predicate: (entity) => command.predicate(entity),
22086
+ predicate: (entity) => typeof entity === "object" && command.predicate(entity),
21541
22087
  getCommand: (entity) => {
21542
22088
  return {
21543
22089
  displayName: entry.label,
@@ -22096,5 +22642,5 @@ const TextAreaPropertyLine = (props) => {
22096
22642
  // Attach Inspector v2 to Scene.debugLayer as a side effect for back compat.
22097
22643
  AttachDebugLayer();
22098
22644
 
22099
- 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 };
22100
- //# sourceMappingURL=index-DrQrky1o.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