@babylonjs/inspector 9.9.1 → 9.9.2

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, useImperativeHandle, cloneElement, 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, Body1Strong, mergeClasses, useId, useToastController, Toast, ToastTitle, FluentProvider, Toaster, Checkbox as Checkbox$1, createLightTheme, createDarkTheme, TeachingPopover, TeachingPopoverSurface, TeachingPopoverHeader, TeachingPopoverBody, createDOMRenderer, RendererProvider, Menu, MenuTrigger, SplitButton, MenuPopover, MenuList, MenuItem, Toolbar as Toolbar$1, ToolbarRadioButton, MenuGroup, MenuGroupHeader, Switch as Switch$1, 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 as Dialog$1, DialogSurface, DialogBody, DialogTitle, DialogContent, DialogActions, List as List$1, ListItem, Badge, Label, MessageBarTitle, useComboboxFilter, Combobox, Subtitle2, Textarea as Textarea$1, ToolbarButton, ToolbarDivider, DialogTrigger, 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, WarningRegular, ArrowExpandAllRegular, ArrowCollapseAllRegular, CubeTreeRegular, BugRegular, ArrowUploadRegular, ArrowBidirectionalUpDownFilled, ArrowDownloadRegular, StopRegular, RecordRegular, DataBarHorizontalRegular, WrenchRegular, ArrowClockwiseRegular, WeatherSunnyRegular, WeatherMoonRegular, PlugDisconnectedRegular, PlugConnectedRegular, PlugConnectedCheckmarkRegular, 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, BubbleMultipleRegular, LightbulbRegular, VideoFilled, VideoRegular, FlashlightRegular, FlashlightOffRegular, DropRegular, BlurRegular, PipelineRegular, PersonWalkingRegular, DataLineRegular, SoundWaveCircleRegular, PersonSquareRegular, LayerDiagonalPersonRegular, ImageEditRegular, ImageRegular, LinkRegular, ArrowSyncRegular, TargetRegular, PersonFeedbackRegular, DismissRegular, 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, Body1Strong, mergeClasses, useId, useToastController, Toast, ToastTitle, FluentProvider, Toaster, Checkbox as Checkbox$1, createLightTheme, createDarkTheme, createDOMRenderer, RendererProvider, TeachingPopover, TeachingPopoverSurface, TeachingPopoverHeader, TeachingPopoverBody, Menu, MenuTrigger, SplitButton, MenuPopover, MenuList, MenuItem, Toolbar as Toolbar$1, ToolbarRadioButton, MenuGroup, MenuGroupHeader, Switch as Switch$1, treeItemLevelToken, typographyStyles, 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 as Dialog$1, DialogSurface, DialogBody, DialogTitle, DialogContent, DialogActions, List as List$1, ListItem, Badge, Label, MessageBarTitle, useComboboxFilter, Combobox, Subtitle2, Textarea as Textarea$1, ToolbarButton, ToolbarDivider, DialogTrigger, 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, WarningRegular, ArrowExpandAllRegular, ArrowCollapseAllRegular, CubeTreeRegular, BugRegular, ArrowUploadRegular, ArrowBidirectionalUpDownFilled, ArrowDownloadRegular, StopRegular, RecordRegular, DataBarHorizontalRegular, WrenchRegular, ArrowClockwiseRegular, WeatherSunnyRegular, WeatherMoonRegular, InfoRegular, CheckmarkCircleRegular, PlugDisconnectedRegular, PlugConnectedRegular, PlugConnectedCheckmarkRegular, 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, HeadphonesSoundWaveRegular, ArrowEnterUpRegular, SoundWaveCircleRegular, SoundWaveCircleFilled, CatchUpRegular, LayerRegular, FrameRegular, AppGenericRegular, RectangleLandscapeRegular, BorderOutsideRegular, BorderNoneRegular, MyLocationRegular, BubbleMultipleRegular, LightbulbRegular, VideoFilled, VideoRegular, FlashlightRegular, FlashlightOffRegular, DropRegular, BlurRegular, PipelineRegular, PersonWalkingRegular, DataLineRegular, PersonSquareRegular, LayerDiagonalPersonRegular, ImageEditRegular, ImageRegular, LinkRegular, ArrowSyncRegular, TargetRegular, PersonFeedbackRegular, DismissRegular, 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';
@@ -42,6 +42,7 @@ import { FrameGraphUtils, FindMainCamera } from '@babylonjs/core/FrameGraph/fram
42
42
  import { CameraGizmo } from '@babylonjs/core/Gizmos/cameraGizmo.js';
43
43
  import { GizmoManager } from '@babylonjs/core/Gizmos/gizmoManager.js';
44
44
  import { LightGizmo } from '@babylonjs/core/Gizmos/lightGizmo.js';
45
+ import { SpatialAudioGizmo } from '@babylonjs/core/Gizmos/spatialAudioGizmo.js';
45
46
  import { Light } from '@babylonjs/core/Lights/light.js';
46
47
  import { AbstractMesh } from '@babylonjs/core/Meshes/abstractMesh.js';
47
48
  import { Node as Node$1 } from '@babylonjs/core/node.js';
@@ -53,6 +54,13 @@ import { AnimationGroup, TargetedAnimation } from '@babylonjs/core/Animations/an
53
54
  import { Animation } from '@babylonjs/core/Animations/animation.js';
54
55
  import { AnimationPropertiesOverride } from '@babylonjs/core/Animations/animationPropertiesOverride.js';
55
56
  import { Sound } from '@babylonjs/core/Audio/sound.js';
57
+ import { AbstractAudioBus } from '@babylonjs/core/AudioV2/abstractAudio/abstractAudioBus.js';
58
+ import { AbstractSound } from '@babylonjs/core/AudioV2/abstractAudio/abstractSound.js';
59
+ import { AbstractSoundSource } from '@babylonjs/core/AudioV2/abstractAudio/abstractSoundSource.js';
60
+ import { AudioBus } from '@babylonjs/core/AudioV2/abstractAudio/audioBus.js';
61
+ import { AudioEngineV2, OnAudioEngineV2CreatedObservable, LastCreatedAudioEngine } from '@babylonjs/core/AudioV2/abstractAudio/audioEngineV2.js';
62
+ import { StaticSound } from '@babylonjs/core/AudioV2/abstractAudio/staticSound.js';
63
+ import { StreamingSound } from '@babylonjs/core/AudioV2/abstractAudio/streamingSound.js';
56
64
  import { ArcRotateCamera } from '@babylonjs/core/Cameras/arcRotateCamera.js';
57
65
  import { FollowCamera } from '@babylonjs/core/Cameras/followCamera.js';
58
66
  import { FreeCamera } from '@babylonjs/core/Cameras/freeCamera.js';
@@ -135,6 +143,7 @@ import { EnvCubeTexture } from '@babylonjs/core/Materials/Textures/envCubeTextur
135
143
  import { MultiRenderTarget } from '@babylonjs/core/Materials/Textures/multiRenderTarget.js';
136
144
  import { RenderTargetTexture } from '@babylonjs/core/Materials/Textures/renderTargetTexture.js';
137
145
  import { ThinTexture } from '@babylonjs/core/Materials/Textures/thinTexture.js';
146
+ import { MainAudioBus } from '@babylonjs/core/AudioV2/abstractAudio/mainAudioBus.js';
138
147
  import '@babylonjs/core/Rendering/boundingBoxRenderer.js';
139
148
  import '@babylonjs/core/PostProcesses/RenderPipeline/postProcessRenderPipelineManagerSceneComponent.js';
140
149
  import '@babylonjs/core/Sprites/spriteSceneComponent.js';
@@ -248,11 +257,11 @@ function ValidateColorHex(val) {
248
257
  // forwardRef wrapper to avoid "function components cannot be given refs" warning
249
258
  // FluentTooltip handles ref forwarding to children internally via applyTriggerPropsToChildren
250
259
  const Tooltip = forwardRef((props, _ref) => {
251
- const { content, children } = props;
260
+ const { content, positioning, children } = props;
252
261
  if (!content) {
253
262
  return children;
254
263
  }
255
- return (jsx(Tooltip$1, { relationship: "description", content: content, children: children }));
264
+ return (jsx(Tooltip$1, { relationship: "description", content: content, positioning: positioning, children: children }));
256
265
  });
257
266
  Tooltip.displayName = "Tooltip";
258
267
 
@@ -268,7 +277,7 @@ const Button = forwardRef((props, ref) => {
268
277
  const { size } = useContext(ToolContext);
269
278
  const classes = useButtonStyles();
270
279
  // eslint-disable-next-line @typescript-eslint/naming-convention
271
- const { icon: Icon, label, onClick, disabled, className, title, ...buttonProps } = props;
280
+ const { icon: Icon, label, onClick, disabled, className, title, ariaLabel, ...buttonProps } = props;
272
281
  const [isOnClickBusy, setIsOnClickBusy] = useState(false);
273
282
  const handleOnClick = useCallback(async (e) => {
274
283
  const result = onClick?.(e);
@@ -283,11 +292,11 @@ const Button = forwardRef((props, ref) => {
283
292
  }
284
293
  }, [onClick]);
285
294
  const iconClass = size === "small" ? classes.smallIcon : classes.mediumIcon;
286
- return (jsx(Tooltip, { content: title ?? "", children: jsx(Button$1, { ref: ref, iconPosition: "after", ...buttonProps, className: className, size: size, icon: isOnClickBusy ? jsx(Spinner, { size: "extra-tiny" }) : Icon && jsx(Icon, { className: iconClass }), onClick: handleOnClick, disabled: disabled || isOnClickBusy, children: label && props.label }) }));
295
+ return (jsx(Tooltip, { content: title ?? "", children: jsx(Button$1, { ref: ref, iconPosition: "after", ...buttonProps, className: className, size: size, "aria-label": ariaLabel ?? (!label ? title : undefined), icon: isOnClickBusy ? jsx(Spinner, { size: "extra-tiny" }) : Icon && jsx(Icon, { className: iconClass }), onClick: handleOnClick, disabled: disabled || isOnClickBusy, children: label && props.label }) }));
287
296
  });
288
297
  Button.displayName = "Button";
289
298
 
290
- const useStyles$10 = makeStyles({
299
+ const useStyles$11 = makeStyles({
291
300
  root: {
292
301
  display: "flex",
293
302
  flexDirection: "column",
@@ -296,6 +305,9 @@ const useStyles$10 = makeStyles({
296
305
  padding: TokenMap.px20,
297
306
  backgroundColor: tokens.colorNeutralBackground1,
298
307
  color: tokens.colorNeutralForeground1,
308
+ // Claim the full row of the flex parent (e.g. shellService's central-content row)
309
+ // so the centered children sit in the middle of the available area, not at the left edge.
310
+ flex: 1,
299
311
  height: "100%",
300
312
  minHeight: "100px",
301
313
  },
@@ -368,7 +380,7 @@ class ErrorBoundary extends Component {
368
380
  }
369
381
  }
370
382
  function ErrorFallback({ error, onRetry }) {
371
- const styles = useStyles$10();
383
+ const styles = useStyles$11();
372
384
  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 })] }));
373
385
  }
374
386
 
@@ -466,9 +478,13 @@ function InterceptProperty(target, propertyKey, hooks) {
466
478
  // Replace the property with a new one that calls the hooks in addition to the original getter and setter.
467
479
  !Reflect.defineProperty(target, propertyKey, {
468
480
  configurable: true,
469
- get: getValue ? () => getValue.call(target) : undefined,
470
- set: (newValue) => {
471
- setValue.call(target, newValue);
481
+ get: getValue
482
+ ? function () {
483
+ return getValue.call(this);
484
+ }
485
+ : undefined,
486
+ set: function (newValue) {
487
+ setValue.call(this, newValue);
472
488
  for (const { afterSet } of hooksForKey) {
473
489
  afterSet?.(newValue);
474
490
  }
@@ -1369,7 +1385,7 @@ function useIsSectionEmpty(sectionId) {
1369
1385
  return hasItems;
1370
1386
  }
1371
1387
 
1372
- const useStyles$$ = makeStyles({
1388
+ const useStyles$10 = makeStyles({
1373
1389
  accordion: {
1374
1390
  display: "flex",
1375
1391
  flexDirection: "column",
@@ -1461,7 +1477,7 @@ const useStyles$$ = makeStyles({
1461
1477
  */
1462
1478
  const AccordionMenuBar = () => {
1463
1479
  AccordionMenuBar.displayName = "AccordionMenuBar";
1464
- const classes = useStyles$$();
1480
+ const classes = useStyles$10();
1465
1481
  const accordionCtx = useContext(AccordionContext);
1466
1482
  if (!accordionCtx) {
1467
1483
  return null;
@@ -1485,7 +1501,7 @@ const AccordionMenuBar = () => {
1485
1501
  const AccordionSectionBlock = (props) => {
1486
1502
  AccordionSectionBlock.displayName = "AccordionSectionBlock";
1487
1503
  const { children, sectionId } = props;
1488
- const classes = useStyles$$();
1504
+ const classes = useStyles$10();
1489
1505
  const accordionCtx = useContext(AccordionContext);
1490
1506
  const { context: sectionContext, isEmpty } = useAccordionSectionBlockContext(props);
1491
1507
  if (accordionCtx) {
@@ -1505,7 +1521,7 @@ const AccordionSectionBlock = (props) => {
1505
1521
  const AccordionSectionItem = (props) => {
1506
1522
  AccordionSectionItem.displayName = "AccordionSectionItem";
1507
1523
  const { children, staticItem } = props;
1508
- const classes = useStyles$$();
1524
+ const classes = useStyles$10();
1509
1525
  const accordionCtx = useContext(AccordionContext);
1510
1526
  const itemState = useAccordionSectionItemState(props);
1511
1527
  const [ctrlMode, setCtrlMode] = useState(false);
@@ -1545,7 +1561,7 @@ const AccordionSectionItem = (props) => {
1545
1561
  */
1546
1562
  const AccordionPinnedContainer = () => {
1547
1563
  AccordionPinnedContainer.displayName = "AccordionPinnedContainer";
1548
- const classes = useStyles$$();
1564
+ const classes = useStyles$10();
1549
1565
  const accordionCtx = useContext(AccordionContext);
1550
1566
  return (jsx("div", { ref: accordionCtx?.pinnedContainerRef, className: classes.pinnedContainer, children: jsx(MessageBar$1, { className: classes.pinnedContainerEmpty, children: jsx(MessageBarBody, { children: "No pinned items" }) }) }));
1551
1567
  };
@@ -1556,7 +1572,7 @@ const AccordionPinnedContainer = () => {
1556
1572
  */
1557
1573
  const AccordionSearchBox = () => {
1558
1574
  AccordionSearchBox.displayName = "AccordionSearchBox";
1559
- const classes = useStyles$$();
1575
+ const classes = useStyles$10();
1560
1576
  const accordionCtx = useContext(AccordionContext);
1561
1577
  if (!accordionCtx?.features.search) {
1562
1578
  return null;
@@ -1572,7 +1588,7 @@ const AccordionSearchBox = () => {
1572
1588
  */
1573
1589
  const AccordionSection = (props) => {
1574
1590
  AccordionSection.displayName = "AccordionSection";
1575
- const classes = useStyles$$();
1591
+ const classes = useStyles$10();
1576
1592
  return jsx("div", { className: classes.panelDiv, children: props.children });
1577
1593
  };
1578
1594
  const StringAccordion = Accordion$1;
@@ -1580,7 +1596,7 @@ const Accordion = forwardRef((props, ref) => {
1580
1596
  Accordion.displayName = "Accordion";
1581
1597
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
1582
1598
  const { children, highlightSections, uniqueId, enablePinnedItems, enableHiddenItems, enableSearchItems, ...rest } = props;
1583
- const classes = useStyles$$();
1599
+ const classes = useStyles$10();
1584
1600
  const { size } = useContext(ToolContext);
1585
1601
  const accordionCtx = useAccordionContext(props);
1586
1602
  const hasPinning = accordionCtx?.features.pinning ?? false;
@@ -1677,7 +1693,7 @@ const Collapse = (props) => {
1677
1693
  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 }) }));
1678
1694
  };
1679
1695
 
1680
- const useStyles$_ = makeStyles({
1696
+ const useStyles$$ = makeStyles({
1681
1697
  button: {
1682
1698
  display: "flex",
1683
1699
  alignItems: "center",
@@ -1693,9 +1709,9 @@ const useStyles$_ = makeStyles({
1693
1709
  */
1694
1710
  const ToggleButton = (props) => {
1695
1711
  ToggleButton.displayName = "ToggleButton";
1696
- const { value, onChange, title, appearance = "subtle" } = props;
1712
+ const { value, onChange, title, appearance = "subtle", ariaLabel } = props;
1697
1713
  const { size } = useContext(ToolContext);
1698
- const classes = useStyles$_();
1714
+ const classes = useStyles$$();
1699
1715
  const [checked, setChecked] = useState(value);
1700
1716
  const toggle = useCallback(() => {
1701
1717
  setChecked((prevChecked) => {
@@ -1707,7 +1723,7 @@ const ToggleButton = (props) => {
1707
1723
  useEffect(() => {
1708
1724
  setChecked(props.value);
1709
1725
  }, [props.value]);
1710
- return (jsx(Tooltip, { content: title ?? "", children: jsx(ToggleButton$1, { className: classes.button, size: size, icon: checked ? jsx(props.checkedIcon, {}) : props.uncheckedIcon ? jsx(props.uncheckedIcon, {}) : jsx(props.checkedIcon, {}), appearance: appearance, checked: checked, onClick: toggle }) }));
1726
+ return (jsx(Tooltip, { content: title ?? "", positioning: props.titlePositioning, children: jsx(ToggleButton$1, { className: classes.button, size: size, "aria-label": ariaLabel ?? title, icon: checked ? jsx(props.checkedIcon, {}) : props.uncheckedIcon ? jsx(props.uncheckedIcon, {}) : jsx(props.checkedIcon, {}), appearance: appearance, checked: checked, onClick: toggle }) }));
1711
1727
  };
1712
1728
 
1713
1729
  const useInfoLabelStyles = makeStyles({
@@ -2004,7 +2020,7 @@ const UXContextProvider = (props) => {
2004
2020
  function AsReadonlyArray(array) {
2005
2021
  return array;
2006
2022
  }
2007
- const useStyles$Z = makeStyles({
2023
+ const useStyles$_ = makeStyles({
2008
2024
  rootDiv: {
2009
2025
  flex: 1,
2010
2026
  overflow: "hidden",
@@ -2019,7 +2035,7 @@ const useStyles$Z = makeStyles({
2019
2035
  * @returns The extensible accordion component.
2020
2036
  */
2021
2037
  function ExtensibleAccordion(props) {
2022
- const classes = useStyles$Z();
2038
+ const classes = useStyles$_();
2023
2039
  const { children, sections, sectionContent, context, sectionsRef, ...rest } = props;
2024
2040
  const defaultSections = useMemo(() => {
2025
2041
  const defaultSections = [];
@@ -2144,7 +2160,7 @@ function ExtensibleAccordion(props) {
2144
2160
  })] }) })) }));
2145
2161
  }
2146
2162
 
2147
- const useStyles$Y = makeStyles({
2163
+ const useStyles$Z = makeStyles({
2148
2164
  paneRootDiv: {
2149
2165
  display: "flex",
2150
2166
  flex: 1,
@@ -2157,7 +2173,7 @@ const useStyles$Y = makeStyles({
2157
2173
  */
2158
2174
  const SidePaneContainer = forwardRef((props, ref) => {
2159
2175
  const { className, ...rest } = props;
2160
- const classes = useStyles$Y();
2176
+ const classes = useStyles$Z();
2161
2177
  return (jsx("div", { className: mergeClasses(classes.paneRootDiv, className), ref: ref, ...rest, children: props.children }));
2162
2178
  });
2163
2179
 
@@ -2428,7 +2444,7 @@ function useTheme(invert = false) {
2428
2444
  }
2429
2445
 
2430
2446
  // Fluent doesn't apply styling to scrollbars by default, so provide our own reasonable default.
2431
- const useStyles$X = makeStyles({
2447
+ const useStyles$Y = makeStyles({
2432
2448
  root: {
2433
2449
  scrollbarColor: `${tokens.colorNeutralForeground3} ${tokens.colorTransparentBackground}`,
2434
2450
  },
@@ -2436,19 +2452,42 @@ const useStyles$X = makeStyles({
2436
2452
  /**
2437
2453
  * A themed Fluent UI provider that applies the current theme mode (light or dark).
2438
2454
  * @param props Fluent provider props, plus an optional `invert` flag to swap the theme.
2455
+ * When `targetDocument` is provided and differs from the inherited Fluent
2456
+ * document (e.g. when rendering into a popup window), a Griffel renderer
2457
+ * scoped to that document is created so styles are injected into it.
2458
+ * When omitted, `targetDocument` is inherited from the ambient Fluent
2459
+ * context so nested Theme components do not lose cross-window targeting.
2439
2460
  * @returns The themed Fluent UI provider component.
2440
2461
  */
2441
2462
  const Theme = (props) => {
2442
2463
  // NOTE: We do not want to applyStylesToPortals by default. It makes classes flow into portals
2443
2464
  // (like popovers), and if those styles do things like disable overflow, they can completely
2444
2465
  // break any UI within the portal. Therefore, default to false.
2445
- const { invert = false, applyStylesToPortals = false, className, ...rest } = props;
2466
+ const { invert = false, applyStylesToPortals = false, className, targetDocument: explicitTargetDocument, ...rest } = props;
2446
2467
  const theme = useTheme(invert);
2447
- const classes = useStyles$X();
2448
- return (jsx(FluentProvider, { theme: theme, className: mergeClasses(classes.root, className), applyStylesToPortals: applyStylesToPortals, ...rest, children: props.children }));
2468
+ const classes = useStyles$Y();
2469
+ // Resolve the target document from the explicit prop or fall back to the ambient Fluent context.
2470
+ // This makes nested <Theme> components automatically inherit cross-window targeting from a
2471
+ // top-level <Theme targetDocument={popupDocument}> wrapper.
2472
+ const inheritedTargetDocument = useFluent().targetDocument;
2473
+ const resolvedTargetDocument = explicitTargetDocument ?? inheritedTargetDocument;
2474
+ // Only create a new Griffel renderer when the resolved document differs from the inherited
2475
+ // one. In the common (main-window, no nesting) case, this leaves Fluent's default renderer
2476
+ // and renderer provider in place — matching the original behaviour exactly.
2477
+ const renderer = useMemo(() => {
2478
+ if (resolvedTargetDocument && resolvedTargetDocument !== inheritedTargetDocument) {
2479
+ return createDOMRenderer(resolvedTargetDocument);
2480
+ }
2481
+ return undefined;
2482
+ }, [resolvedTargetDocument, inheritedTargetDocument]);
2483
+ const fluent = (jsx(FluentProvider, { theme: theme, className: mergeClasses(classes.root, className), applyStylesToPortals: applyStylesToPortals, targetDocument: resolvedTargetDocument, ...rest, children: props.children }));
2484
+ if (renderer && resolvedTargetDocument) {
2485
+ return (jsx(RendererProvider, { renderer: renderer, targetDocument: resolvedTargetDocument, children: fluent }));
2486
+ }
2487
+ return fluent;
2449
2488
  };
2450
2489
 
2451
- const useStyles$W = makeStyles({
2490
+ const useStyles$X = makeStyles({
2452
2491
  extensionTeachingPopover: {
2453
2492
  maxWidth: "320px",
2454
2493
  },
@@ -2459,7 +2498,7 @@ const useStyles$W = makeStyles({
2459
2498
  * @returns The teaching moment popover.
2460
2499
  */
2461
2500
  const TeachingMoment = ({ shouldDisplay, positioningRef, onOpenChange, title, description }) => {
2462
- const classes = useStyles$W();
2501
+ const classes = useStyles$X();
2463
2502
  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 })] }) }));
2464
2503
  };
2465
2504
 
@@ -2684,24 +2723,161 @@ function ConstructorFactory(constructor) {
2684
2723
  return (...args) => new constructor(...args);
2685
2724
  }
2686
2725
 
2687
- function ToFeaturesString(options) {
2688
- const { defaultWidth, defaultHeight, defaultLeft, defaultTop } = options;
2689
- const features = [];
2690
- if (defaultWidth !== undefined) {
2691
- features.push({ key: "width", value: defaultWidth.toString() });
2726
+ const StorageKeyPrefix = "Babylon/Settings/PopupWindow";
2727
+ function LoadSavedBounds(id) {
2728
+ const stored = localStorage.getItem(`${StorageKeyPrefix}/${id}/Bounds`);
2729
+ if (!stored) {
2730
+ return null;
2731
+ }
2732
+ try {
2733
+ const parsed = JSON.parse(stored);
2734
+ return parsed;
2735
+ }
2736
+ catch {
2737
+ Logger.Warn(`Could not parse saved bounds for popup window with id ${id}`);
2738
+ return null;
2692
2739
  }
2693
- if (defaultHeight !== undefined) {
2694
- features.push({ key: "height", value: defaultHeight.toString() });
2740
+ }
2741
+ function SaveBounds(id, bounds) {
2742
+ try {
2743
+ localStorage.setItem(`${StorageKeyPrefix}/${id}/Bounds`, JSON.stringify(bounds));
2695
2744
  }
2696
- if (defaultLeft !== undefined) {
2697
- features.push({ key: "left", value: defaultLeft.toString() });
2745
+ catch {
2746
+ // Storage may be full / disabled — bounds simply won't persist.
2698
2747
  }
2699
- if (defaultTop !== undefined) {
2700
- features.push({ key: "top", value: defaultTop.toString() });
2748
+ }
2749
+ function ResolveBounds(options) {
2750
+ const saved = options.id ? LoadSavedBounds(options.id) : null;
2751
+ const width = options.defaultWidth ?? saved?.width ?? Math.floor(window.innerWidth * (2 / 3));
2752
+ const height = options.defaultHeight ?? saved?.height ?? Math.floor(window.innerHeight * (2 / 3));
2753
+ const left = options.defaultLeft ?? saved?.left ?? Math.floor(window.screenX + (window.innerWidth - width) / 2);
2754
+ const top = options.defaultTop ?? saved?.top ?? Math.floor(window.screenY + (window.innerHeight - height) / 2);
2755
+ // When the caller passes explicit width/height/left/top, always honour them; otherwise fall
2756
+ // back to the (saved or computed) values above. The order above already gives explicit
2757
+ // options precedence, so just build the resulting bounds now.
2758
+ return { left, top, width, height };
2759
+ }
2760
+ function ToFeaturesString(bounds) {
2761
+ return `width=${bounds.width},height=${bounds.height},left=${bounds.left},top=${bounds.top},location=no`;
2762
+ }
2763
+ /**
2764
+ * Opens a new browser popup window suitable for hosting a Fluent-based modular tool.
2765
+ *
2766
+ * The popup body is configured for full-bleed flex layout and a host `<div>` is appended
2767
+ * for the tool to render into. Fluent style targeting (Griffel `RendererProvider`,
2768
+ * `FluentProvider` with `targetDocument`) is the caller's responsibility — typically wired
2769
+ * up by `MakeModularTool`, which derives `targetDocument` from `containerElement.ownerDocument`.
2770
+ *
2771
+ * **Must be called synchronously in response to a user interaction** (e.g. button click) —
2772
+ * otherwise the browser will block the popup as a scripted popup.
2773
+ *
2774
+ * @param options Window options. See {@link PopupWindowOptions}.
2775
+ * @returns A handle to the popup window and its host element, plus a `dispose` to close it.
2776
+ * `null` if the popup was blocked by the browser.
2777
+ */
2778
+ function OpenPopupWindow(options = {}) {
2779
+ const bounds = ResolveBounds(options);
2780
+ const popupWindow = window.open("", "", ToFeaturesString(bounds));
2781
+ if (!popupWindow) {
2782
+ return null;
2701
2783
  }
2702
- features.push({ key: "location", value: "no" });
2703
- return features.map((feature) => `${feature.key}=${feature.value}`).join(",");
2784
+ if (options.title) {
2785
+ popupWindow.document.title = options.title;
2786
+ }
2787
+ const popupBody = popupWindow.document.body;
2788
+ popupBody.style.width = "100%";
2789
+ popupBody.style.height = "100%";
2790
+ popupBody.style.margin = "0";
2791
+ popupBody.style.padding = "0";
2792
+ popupBody.style.display = "flex";
2793
+ popupBody.style.overflow = "hidden";
2794
+ const hostElement = popupWindow.document.createElement("div");
2795
+ hostElement.style.display = "flex";
2796
+ hostElement.style.flexDirection = "column";
2797
+ hostElement.style.flexGrow = "1";
2798
+ hostElement.style.width = "100%";
2799
+ hostElement.style.height = "100%";
2800
+ hostElement.style.margin = "0";
2801
+ hostElement.style.padding = "0";
2802
+ hostElement.style.overflow = "hidden";
2803
+ popupBody.appendChild(hostElement);
2804
+ // Track the most recently observed window bounds. In some browsers (e.g. Firefox), accessing
2805
+ // properties like screenX on a closed window throws, so we cache the last known good values
2806
+ // to use as a fallback when saving after the window has already been closed.
2807
+ const getBounds = () => ({
2808
+ left: popupWindow.screenX,
2809
+ top: popupWindow.screenY,
2810
+ width: popupWindow.innerWidth,
2811
+ height: popupWindow.innerHeight,
2812
+ });
2813
+ let lastBounds = bounds;
2814
+ const onPopupBeforeUnload = () => {
2815
+ try {
2816
+ lastBounds = getBounds();
2817
+ }
2818
+ catch {
2819
+ // Use the cached lastBounds.
2820
+ }
2821
+ };
2822
+ popupWindow.addEventListener("beforeunload", onPopupBeforeUnload);
2823
+ let disposed = false;
2824
+ // Internal cleanup: tears down listeners, persists bounds, closes the popup.
2825
+ // Does NOT invoke `options.onClose` — that's reserved for popup unload events
2826
+ // that originate from outside our own teardown (e.g. user dismissed the popup).
2827
+ const cleanup = () => {
2828
+ if (disposed) {
2829
+ return;
2830
+ }
2831
+ disposed = true;
2832
+ if (options.id) {
2833
+ try {
2834
+ if (!popupWindow.closed) {
2835
+ lastBounds = getBounds();
2836
+ }
2837
+ }
2838
+ catch {
2839
+ // Use the cached lastBounds.
2840
+ }
2841
+ SaveBounds(options.id, lastBounds);
2842
+ }
2843
+ popupWindow.removeEventListener("beforeunload", onPopupBeforeUnload);
2844
+ // Remove the unload listener so that any pending unload event triggered by our
2845
+ // own popupWindow.close() below cannot reach back into onPopupUnload after we've
2846
+ // already torn down. This avoids a race where, during a programmatic open-while-open
2847
+ // swap, the old popup's unload would otherwise fire onClose and clear React state
2848
+ // pointing at the new popup.
2849
+ popupWindow.removeEventListener("unload", onPopupUnload);
2850
+ window.removeEventListener("unload", onParentUnload);
2851
+ if (!popupWindow.closed) {
2852
+ popupWindow.close();
2853
+ }
2854
+ };
2855
+ const onPopupUnload = () => {
2856
+ if (disposed) {
2857
+ // Already torn down programmatically; the popup is just finishing its unload.
2858
+ return;
2859
+ }
2860
+ cleanup();
2861
+ // Notify the consumer only for externally-triggered closures (user dismissed the popup,
2862
+ // tab/browser closed). Programmatic disposal calls cleanup() directly and intentionally
2863
+ // skips this so callers don't get a re-entrant onClose for the close they themselves issued.
2864
+ options.onClose?.();
2865
+ };
2866
+ popupWindow.addEventListener("unload", onPopupUnload, { once: true });
2867
+ // If the parent window is unloaded (page refresh / navigation), don't leave the popup orphaned.
2868
+ const onParentUnload = () => {
2869
+ if (!popupWindow.closed) {
2870
+ popupWindow.close();
2871
+ }
2872
+ };
2873
+ window.addEventListener("unload", onParentUnload, { once: true });
2874
+ return {
2875
+ popupWindow,
2876
+ hostElement,
2877
+ dispose: cleanup,
2878
+ };
2704
2879
  }
2880
+
2705
2881
  /**
2706
2882
  * Allows displaying a child window that can contain child components.
2707
2883
  * @param props Props for the child window.
@@ -2710,133 +2886,66 @@ function ToFeaturesString(options) {
2710
2886
  const ChildWindow = (props) => {
2711
2887
  const { id, children, onOpenChange, imperativeRef: imperativeRef } = props;
2712
2888
  const [windowState, setWindowState] = useState();
2713
- const [childWindow, setChildWindow] = useState();
2714
- const storageKey = id ? `Babylon/Settings/ChildWindow/${id}/Bounds` : null;
2889
+ const [popupHandle, setPopupHandle] = useState();
2715
2890
  // This function is just for creating the child window itself. It is a function because
2716
2891
  // it must be called synchronously in response to a user interaction (e.g. button click),
2717
2892
  // otherwise the browser will block it as a scripted popup.
2718
2893
  const createWindow = useCallback((options = {}) => {
2719
- if (storageKey) {
2720
- // If we are persisting window bounds, but the window is already open, just use the existing bounds.
2721
- // Otherwise, try to load bounds from storage.
2722
- if (childWindow) {
2723
- options.defaultLeft = childWindow.screenX;
2724
- options.defaultTop = childWindow.screenY;
2725
- options.defaultWidth = childWindow.innerWidth;
2726
- options.defaultHeight = childWindow.innerHeight;
2727
- }
2728
- else {
2729
- const savedBounds = localStorage.getItem(storageKey);
2730
- if (savedBounds) {
2731
- try {
2732
- const bounds = JSON.parse(savedBounds);
2733
- options.defaultLeft = bounds.left;
2734
- options.defaultTop = bounds.top;
2735
- options.defaultWidth = bounds.width;
2736
- options.defaultHeight = bounds.height;
2737
- }
2738
- catch {
2739
- Logger.Warn(`Could not parse saved bounds for child window with key ${storageKey}`);
2740
- }
2741
- }
2742
- }
2743
- }
2744
- // Half width by default.
2745
- if (!options.defaultWidth) {
2746
- options.defaultWidth = window.innerWidth * (2 / 3);
2747
- }
2748
- // Half height by default.
2749
- if (!options.defaultHeight) {
2750
- options.defaultHeight = window.innerHeight * (2 / 3);
2751
- }
2752
- // Horizontally centered by default.
2753
- if (!options.defaultLeft) {
2754
- options.defaultLeft = window.screenX + (window.innerWidth - options.defaultWidth) * (2 / 3);
2755
- }
2756
- // Vertically centered by default.
2757
- if (!options.defaultTop) {
2758
- options.defaultTop = window.screenY + (window.innerHeight - options.defaultHeight) * (2 / 3);
2759
- }
2760
- // Try to create the child window (can be null if popups are blocked).
2761
- const newChildWindow = window.open("", "", ToFeaturesString(options));
2762
- if (newChildWindow) {
2763
- // Set the title if provided.
2764
- newChildWindow.document.title = options.title ?? id ?? "";
2765
- // Set the child window state.
2766
- setChildWindow((current) => {
2767
- // But first close any existing child window.
2768
- current?.close();
2769
- return newChildWindow;
2894
+ const handle = OpenPopupWindow({
2895
+ id,
2896
+ title: options.title ?? id,
2897
+ defaultWidth: options.defaultWidth,
2898
+ defaultHeight: options.defaultHeight,
2899
+ defaultLeft: options.defaultLeft,
2900
+ defaultTop: options.defaultTop,
2901
+ onClose: () => {
2902
+ // Triggered when the popup is closed for any reason (user dismissal, parent unload,
2903
+ // or programmatic dispose). Clear the popup handle so the effect cleanup runs and
2904
+ // `onOpenChange(false)` is propagated up the tree (which lets a parent shell re-dock
2905
+ // a previously-undocked pane). teardown() is idempotent so calling dispose() again
2906
+ // from the effect cleanup is a safe no-op.
2907
+ setPopupHandle(undefined);
2908
+ },
2909
+ });
2910
+ if (handle) {
2911
+ setPopupHandle((current) => {
2912
+ // Close any existing child window before adopting the new one.
2913
+ current?.dispose();
2914
+ return handle;
2770
2915
  });
2771
2916
  }
2772
- }, [childWindow, storageKey]);
2917
+ }, [id]);
2773
2918
  useImperativeHandle(imperativeRef, () => {
2774
2919
  return {
2775
2920
  open: createWindow,
2776
- close: () => setChildWindow(undefined),
2921
+ close: () => {
2922
+ setPopupHandle((current) => {
2923
+ current?.dispose();
2924
+ return undefined;
2925
+ });
2926
+ },
2777
2927
  };
2778
2928
  }, [createWindow]);
2779
- // This side effect runs any time the child window instance changes. It does the rest of the child window
2929
+ // This side effect runs any time the popup handle changes. It does the rest of the child window
2780
2930
  // setup work, including creating resources and state needed to properly render the content of the child window.
2781
2931
  useEffect(() => {
2782
- const disposeActions = [];
2783
- if (childWindow) {
2784
- const body = childWindow.document.body;
2785
- body.style.width = "100%";
2786
- body.style.height = "100%";
2787
- body.style.margin = "0";
2788
- body.style.padding = "0";
2789
- body.style.display = "flex";
2790
- body.style.overflow = "hidden";
2791
- // Setup the window state, including creating a Fluent/Griffel "renderer" for managing runtime styles/classes in the child window.
2792
- setWindowState({ mountNode: body, renderer: createDOMRenderer(childWindow.document) });
2793
- onOpenChange?.(true);
2794
- // Track the most recently observed window bounds. In some browsers (e.g. Firefox), accessing
2795
- // properties like screenX on a closed window throws, so we cache the last known good values
2796
- // to use as a fallback when the dispose runs after the window has already been closed.
2797
- const getBounds = () => ({
2798
- left: childWindow.screenX,
2799
- top: childWindow.screenY,
2800
- width: childWindow.innerWidth,
2801
- height: childWindow.innerHeight,
2802
- });
2803
- let lastBounds = getBounds();
2804
- // When the child window is closed for any reason, transition back to a closed state.
2805
- const onChildWindowUnload = () => {
2806
- setWindowState(undefined);
2807
- setChildWindow(undefined);
2808
- onOpenChange?.(false);
2809
- };
2810
- childWindow.addEventListener("unload", onChildWindowUnload, { once: true });
2811
- disposeActions.push(() => childWindow.removeEventListener("unload", onChildWindowUnload));
2812
- // Capture bounds before the window is unloaded, while its properties are still safe to read.
2813
- const onChildWindowBeforeUnload = () => {
2814
- lastBounds = getBounds();
2815
- };
2816
- childWindow.addEventListener("beforeunload", onChildWindowBeforeUnload);
2817
- disposeActions.push(() => childWindow.removeEventListener("beforeunload", onChildWindowBeforeUnload));
2818
- // If the main window closes, close any open child windows as well (don't leave them orphaned).
2819
- const onParentWindowUnload = () => {
2820
- childWindow.close();
2821
- };
2822
- window.addEventListener("unload", onParentWindowUnload, { once: true });
2823
- disposeActions.push(() => window.removeEventListener("unload", onParentWindowUnload));
2824
- // On dispose, close the child window.
2825
- disposeActions.push(() => childWindow.close());
2826
- // On dispose, save the window bounds.
2827
- disposeActions.push(() => {
2828
- if (storageKey) {
2829
- if (!childWindow.closed) {
2830
- lastBounds = getBounds();
2831
- }
2832
- localStorage.setItem(storageKey, JSON.stringify(lastBounds));
2833
- }
2834
- });
2932
+ if (!popupHandle) {
2933
+ return undefined;
2835
2934
  }
2935
+ const popupDocument = popupHandle.popupWindow.document;
2936
+ // Use the popup's hostElement as the React mount point. OpenPopupWindow configures it as a
2937
+ // flex column that fills the popup body, so the FluentProvider (also a flex column with
2938
+ // flex-grow: 1) inside it can fill the available space.
2939
+ setWindowState({ mountNode: popupHandle.hostElement, renderer: createDOMRenderer(popupDocument) });
2940
+ onOpenChange?.(true);
2836
2941
  return () => {
2837
- disposeActions.reverse().forEach((dispose) => dispose());
2942
+ // Tear down the popup. The cached handle's dispose() handles bounds saving and
2943
+ // listener cleanup; React state is reset so the Portal/Provider tree unmounts.
2944
+ popupHandle.dispose();
2945
+ setWindowState(undefined);
2946
+ onOpenChange?.(false);
2838
2947
  };
2839
- }, [childWindow]);
2948
+ }, [popupHandle]);
2840
2949
  if (!windowState) {
2841
2950
  return null;
2842
2951
  }
@@ -2892,7 +3001,7 @@ const RootComponentServiceIdentity = Symbol("RootComponent");
2892
3001
  * The unique identity symbol for the shell service.
2893
3002
  */
2894
3003
  const ShellServiceIdentity = Symbol("ShellService");
2895
- const useStyles$V = makeStyles({
3004
+ const useStyles$W = makeStyles({
2896
3005
  mainView: {
2897
3006
  flex: 1,
2898
3007
  display: "flex",
@@ -2959,6 +3068,7 @@ const useStyles$V = makeStyles({
2959
3068
  paneCollapseButton: {
2960
3069
  padding: `0 0 0 ${tokens.spacingHorizontalXS}`,
2961
3070
  borderBottom: `1px solid ${tokens.colorNeutralStroke2}`,
3071
+ backgroundColor: tokens.colorNeutralBackground2,
2962
3072
  },
2963
3073
  paneCollapseButtonWithBorder: {
2964
3074
  borderLeft: `1px solid ${tokens.colorNeutralStroke2}`,
@@ -2982,6 +3092,11 @@ const useStyles$V = makeStyles({
2982
3092
  paneContainer: {
2983
3093
  display: "flex",
2984
3094
  flexDirection: "column",
3095
+ // Side panes hold the width requested by their `style.width` (or saved/dragged value)
3096
+ // and never give it up to a neighboring pane growing. Without this, dragging the left
3097
+ // pane wider would also squeeze the right pane (proportional flex-shrink). The central
3098
+ // content (`flex-grow: 1`) is the only flex item that absorbs the change.
3099
+ flexShrink: 0,
2985
3100
  overflowX: "hidden",
2986
3101
  overflowY: "hidden",
2987
3102
  zIndex: 1,
@@ -3105,14 +3220,14 @@ const DockMenu = (props) => {
3105
3220
  };
3106
3221
  const PaneHeader = (props) => {
3107
3222
  const { id, title, dockOptions } = props;
3108
- const classes = useStyles$V();
3223
+ const classes = useStyles$W();
3109
3224
  return (jsxs("div", { className: classes.paneHeaderDiv, children: [props.icon && (jsx("div", { className: classes.paneHeaderIcon, children: jsx(props.icon, {}) })), jsx(Subtitle2Stronger, { className: mergeClasses(classes.paneHeaderText, !props.icon && classes.paneHeaderTextNoIcon), children: title }), jsx(DockMenu, { sidePaneId: id, dockOptions: dockOptions, children: jsx(Button$1, { className: classes.paneHeaderButton, appearance: "transparent", icon: jsx(MoreHorizontalRegular, {}) }) })] }));
3110
3225
  };
3111
3226
  // 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.
3112
3227
  const ToolbarItem = (props) => {
3113
3228
  // eslint-disable-next-line @typescript-eslint/naming-convention
3114
3229
  const { verticalLocation, horizontalLocation, id, component: Component, displayName } = props;
3115
- const classes = useStyles$V();
3230
+ const classes = useStyles$W();
3116
3231
  const useTeachingMoment = useMemo(() => MakePopoverTeachingMoment(`Bar/${verticalLocation}/${horizontalLocation}/${displayName ?? id}`), [displayName, id]);
3117
3232
  const teachingMoment = useTeachingMoment(props.teachingMoment === false);
3118
3233
  const title = typeof props.teachingMoment === "object" ? props.teachingMoment.title : (displayName ?? id);
@@ -3122,7 +3237,7 @@ const ToolbarItem = (props) => {
3122
3237
  // TODO: Handle overflow, possibly via https://react.fluentui.dev/?path=/docs/components-overflow--docs with priority.
3123
3238
  // This component just renders a toolbar with left aligned toolbar items on the left and right aligned toolbar items on the right.
3124
3239
  const Toolbar = ({ location, components }) => {
3125
- const classes = useStyles$V();
3240
+ const classes = useStyles$W();
3126
3241
  const leftComponents = useMemo(() => components.filter((entry) => entry.horizontalLocation === "left"), [components]);
3127
3242
  const rightComponents = useMemo(() => components.filter((entry) => entry.horizontalLocation === "right"), [components]);
3128
3243
  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,7 +3247,7 @@ const SidePaneTab = (props) => {
3132
3247
  const { location, id, isSelected, isFirst, isLast, dockOptions,
3133
3248
  // eslint-disable-next-line @typescript-eslint/naming-convention
3134
3249
  icon: Icon, title, } = props;
3135
- const classes = useStyles$V();
3250
+ const classes = useStyles$W();
3136
3251
  const useTeachingMoment = useMemo(() => MakePopoverTeachingMoment(`Pane/${location}/${title ?? id}`), [title, id]);
3137
3252
  const teachingMoment = useTeachingMoment(props.teachingMoment === false);
3138
3253
  const tabClass = mergeClasses(classes.tab, isSelected ? classes.selectedTab : classes.unselectedTab, isFirst ? classes.firstTab : undefined, isLast ? classes.lastTab : undefined);
@@ -3144,7 +3259,7 @@ const SidePaneTab = (props) => {
3144
3259
  // In "compact" mode, the tab list is integrated into the pane itself.
3145
3260
  // In "full" mode, the returned tab list is later injected into the toolbar.
3146
3261
  function usePane(location, defaultWidth, minWidth, sidePanes, onSelectSidePane, dockOperations, toolbarMode, topBarItems, bottomBarItems, initialCollapsed) {
3147
- const classes = useStyles$V();
3262
+ const classes = useStyles$W();
3148
3263
  const [topSelectedTab, setTopSelectedTab] = useState();
3149
3264
  const [bottomSelectedTab, setBottomSelectedTab] = useState();
3150
3265
  const [collapsed, setCollapsed] = useState(initialCollapsed);
@@ -3293,16 +3408,25 @@ function usePane(location, defaultWidth, minWidth, sidePanes, onSelectSidePane,
3293
3408
  // registered). The Fluent hook's setValue will silently fail when the element doesn't exist (in relative mode,
3294
3409
  // it measures the element before/after and reverts if unchanged, which always happens when the element is null).
3295
3410
  // By composing the ref callback, we ensure the stored value is applied immediately after the element mounts.
3411
+ //
3412
+ // We also set the CSS variable directly as a fallback. The hook preserves its internal currentValue across
3413
+ // re-mounts, so when (for example) the user undocks a resized pane and then re-docks it, currentValue already
3414
+ // equals the persisted setting and the hook's setValue short-circuits — but the freshly-mounted DOM node has no
3415
+ // inline CSS variable, so the pane visually reverts to the default width until the user drags. Setting the
3416
+ // variable directly closes that gap. The order is important: setValue first (handles the first-mount case where
3417
+ // currentValue starts at 0), then the direct setProperty as a redundant fallback for the no-op case.
3296
3418
  const composedHorizontalElementRef = useCallback((node) => {
3297
3419
  paneHorizontalResizeElementRef(node);
3298
3420
  if (node) {
3299
3421
  setPaneWidthAdjust(paneWidthSettingRef.current);
3422
+ node.style.setProperty(paneWidthAdjustCSSVar, `${paneWidthSettingRef.current}px`);
3300
3423
  }
3301
3424
  }, [paneHorizontalResizeElementRef, setPaneWidthAdjust]);
3302
3425
  const composedVerticalElementRef = useCallback((node) => {
3303
3426
  paneVerticalResizeElementRef(node);
3304
3427
  if (node) {
3305
3428
  setPaneHeightAdjust(paneHeightSettingRef.current);
3429
+ node.style.setProperty(paneHeightAdjustCSSVar, `${paneHeightSettingRef.current}px`);
3306
3430
  }
3307
3431
  }, [paneVerticalResizeElementRef, setPaneHeightAdjust]);
3308
3432
  // Handle external setting changes (e.g. settings reset) after elements are already mounted.
@@ -3373,7 +3497,7 @@ function MakeShellServiceDefinition({ leftPaneDefaultWidth = 350, leftPaneMinWid
3373
3497
  expand: () => onCollapseChanged.notifyObservers({ location: "right", collapsed: false }),
3374
3498
  };
3375
3499
  const rootComponent = () => {
3376
- const classes = useStyles$V();
3500
+ const classes = useStyles$W();
3377
3501
  const [sidePaneDockOverrides, setSidePaneDockOverrides] = useSetting(SidePaneDockOverridesSettingDescriptor);
3378
3502
  // This function returns a promise that resolves after the dock change takes effect so that
3379
3503
  // we can then select the re-docked pane.
@@ -3760,13 +3884,13 @@ function useImpulse() {
3760
3884
  return [value, pulse];
3761
3885
  }
3762
3886
 
3763
- const useStyles$U = makeStyles({
3887
+ const useStyles$V = makeStyles({
3764
3888
  placeholderDiv: {
3765
3889
  padding: `${tokens.spacingVerticalM} ${tokens.spacingHorizontalM}`,
3766
3890
  },
3767
3891
  });
3768
3892
  const PropertiesPane = (props) => {
3769
- const classes = useStyles$U();
3893
+ const classes = useStyles$V();
3770
3894
  const entity = props.context;
3771
3895
  return entity != null ? (jsx(ExtensibleAccordion, { ...props })) : (jsx("div", { className: classes.placeholderDiv, children: jsx(Body1Strong, { italic: true, children: "No entity selected." }) }));
3772
3896
  };
@@ -4206,7 +4330,7 @@ function CoerceEntityArray(entities, sort) {
4206
4330
  }
4207
4331
  return entities;
4208
4332
  }
4209
- const useStyles$T = makeStyles({
4333
+ const useStyles$U = makeStyles({
4210
4334
  rootDiv: {
4211
4335
  flex: 1,
4212
4336
  overflow: "hidden",
@@ -4281,6 +4405,37 @@ const useStyles$T = makeStyles({
4281
4405
  function GetCommandDescription(command) {
4282
4406
  return command.hotKey ? `${command.displayName} (${GetCommandHotKeyDescription(command)})` : command.displayName;
4283
4407
  }
4408
+ const useTruncatingBody1Styles = makeStyles({
4409
+ container: {
4410
+ display: "block",
4411
+ overflow: "hidden",
4412
+ textOverflow: "ellipsis",
4413
+ whiteSpace: "nowrap",
4414
+ minWidth: 0,
4415
+ ...typographyStyles.body1,
4416
+ },
4417
+ });
4418
+ const TruncatingBody1 = (props) => {
4419
+ const { text } = props;
4420
+ const classes = useTruncatingBody1Styles();
4421
+ const ref = useRef(null);
4422
+ const [isTruncated, setIsTruncated] = useState(false);
4423
+ const [isHovered, setIsHovered] = useState(false);
4424
+ useEffect(() => {
4425
+ const element = ref.current;
4426
+ if (!element) {
4427
+ return undefined;
4428
+ }
4429
+ const update = () => {
4430
+ setIsTruncated(element.scrollWidth > element.clientWidth);
4431
+ };
4432
+ update();
4433
+ const observer = new ResizeObserver(update);
4434
+ observer.observe(element);
4435
+ return () => observer.disconnect();
4436
+ }, [text]);
4437
+ return (jsx(Tooltip$1, { content: text, positioning: "after", relationship: "description", visible: isTruncated && isHovered, onVisibleChange: (_, data) => setIsHovered(data.visible), children: jsx("span", { ref: ref, className: classes.container, children: text }) }));
4438
+ };
4284
4439
  const ActionCommand = (props) => {
4285
4440
  const { command } = props;
4286
4441
  // eslint-disable-next-line @typescript-eslint/naming-convention
@@ -4292,7 +4447,7 @@ const ToggleCommand = (props) => {
4292
4447
  // eslint-disable-next-line @typescript-eslint/naming-convention
4293
4448
  const [Icon, isEnabled] = useObservableState(useCallback(() => [command.icon, command.isEnabled], [command]), command.onChange);
4294
4449
  // TODO-iv2: Consolidate icon prop passing approach for inspector and shared components
4295
- return (jsx(ToggleButton, { appearance: "transparent", title: GetCommandDescription(command), checkedIcon: Icon, value: isEnabled, onChange: (val) => (command.isEnabled = val) }));
4450
+ return (jsx(ToggleButton, { appearance: "transparent", title: GetCommandDescription(command), titlePositioning: "after", checkedIcon: Icon, value: isEnabled, onChange: (val) => (command.isEnabled = val) }));
4296
4451
  };
4297
4452
  // This "placeholder" command has a blank icon and is a no-op. It is used for aside
4298
4453
  // alignment when some toggle commands are enabled. See more details on the commands
@@ -4315,14 +4470,14 @@ function MakeInlineCommandElement(command, isPlaceholder) {
4315
4470
  }
4316
4471
  const SceneTreeItem = (props) => {
4317
4472
  const { isSelected, select } = props;
4318
- const classes = useStyles$T();
4473
+ const classes = useStyles$U();
4319
4474
  const [compactMode] = useSetting(CompactModeSettingDescriptor);
4320
4475
  const treeItemLayoutClass = mergeClasses(classes.sceneTreeItemLayout, compactMode ? classes.treeItemLayoutCompact : undefined);
4321
4476
  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"));
4322
4477
  };
4323
4478
  const SectionTreeItem = (props) => {
4324
4479
  const { section, isFiltering, commandProviders, expandAll, collapseAll, isDropTarget, ...dropProps } = props;
4325
- const classes = useStyles$T();
4480
+ const classes = useStyles$U();
4326
4481
  const [compactMode] = useSetting(CompactModeSettingDescriptor);
4327
4482
  // Get the commands that apply to this section.
4328
4483
  const commands = useResource(useCallback(() => {
@@ -4339,7 +4494,7 @@ const SectionTreeItem = (props) => {
4339
4494
  };
4340
4495
  const EntityTreeItem = (props) => {
4341
4496
  const { entityItem, isSelected, select, isFiltering, commandProviders, expandAll, collapseAll, isDragging, isDropTarget, ...dragProps } = props;
4342
- const classes = useStyles$T();
4497
+ const classes = useStyles$U();
4343
4498
  const [compactMode] = useSetting(CompactModeSettingDescriptor);
4344
4499
  const hasChildren = !!entityItem.children?.length;
4345
4500
  const displayInfo = useResource(useCallback(() => {
@@ -4452,10 +4607,10 @@ const EntityTreeItem = (props) => {
4452
4607
  }, main: {
4453
4608
  // Prevent the "main" content (the Body1 below) from growing too large and pushing the actions/aside out of view.
4454
4609
  className: classes.treeItemLayoutMain,
4455
- }, children: jsx(Tooltip$1, { content: name, relationship: "description", children: jsx(Body1, { wrap: false, truncate: true, children: name }) }) }) }, GetEntityId$1(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] }) })] }));
4610
+ }, children: jsx(TruncatingBody1, { text: name }) }) }, GetEntityId$1(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] }) })] }));
4456
4611
  };
4457
4612
  const SceneExplorer = (props) => {
4458
- const classes = useStyles$T();
4613
+ const classes = useStyles$U();
4459
4614
  const { sections, entityCommandProviders, sectionCommandProviders, scene, selectedEntity = null } = props;
4460
4615
  const [openItems, setOpenItems] = useState(new Set());
4461
4616
  const [sceneVersion, setSceneVersion] = useState(0);
@@ -6081,7 +6236,7 @@ class CanvasGraphService {
6081
6236
  }
6082
6237
  }
6083
6238
 
6084
- const useStyles$S = makeStyles({
6239
+ const useStyles$T = makeStyles({
6085
6240
  canvas: {
6086
6241
  flexGrow: 1,
6087
6242
  width: "100%",
@@ -6090,7 +6245,7 @@ const useStyles$S = makeStyles({
6090
6245
  });
6091
6246
  const CanvasGraph = (props) => {
6092
6247
  const { collector, scene, layoutObservable, returnToPlayheadObservable, onVisibleRangeChangedObservable, initialGraphSize } = props;
6093
- const classes = useStyles$S();
6248
+ const classes = useStyles$T();
6094
6249
  const canvasRef = useRef(null);
6095
6250
  useEffect(() => {
6096
6251
  if (!canvasRef.current) {
@@ -6179,7 +6334,7 @@ function EvaluateExpression(rawValue) {
6179
6334
  return NaN;
6180
6335
  }
6181
6336
  }
6182
- const useStyles$R = makeStyles({
6337
+ const useStyles$S = makeStyles({
6183
6338
  icon: {
6184
6339
  "&:hover": {
6185
6340
  color: tokens.colorBrandForeground1,
@@ -6193,7 +6348,7 @@ const useStyles$R = makeStyles({
6193
6348
  const SpinButton = forwardRef((props, ref) => {
6194
6349
  SpinButton.displayName = "SpinButton2";
6195
6350
  const inputClasses = useInputStyles$1();
6196
- const classes = useStyles$R();
6351
+ const classes = useStyles$S();
6197
6352
  const { size } = useContext(ToolContext);
6198
6353
  const { min, max } = props;
6199
6354
  const baseStep = props.step ?? 1;
@@ -6460,7 +6615,7 @@ const Dropdown = (props) => {
6460
6615
  const NumberDropdown = Dropdown;
6461
6616
  const StringDropdown = Dropdown;
6462
6617
 
6463
- const useStyles$Q = makeStyles({
6618
+ const useStyles$R = makeStyles({
6464
6619
  surface: {
6465
6620
  maxWidth: "400px",
6466
6621
  },
@@ -6475,7 +6630,7 @@ const useStyles$Q = makeStyles({
6475
6630
  const Popover = forwardRef((props, ref) => {
6476
6631
  const { children, open: controlledOpen, onOpenChange, positioning, surfaceClassName } = props;
6477
6632
  const [internalOpen, setInternalOpen] = useState(false);
6478
- const classes = useStyles$Q();
6633
+ const classes = useStyles$R();
6479
6634
  const isControlled = controlledOpen !== undefined;
6480
6635
  const popoverOpen = isControlled ? controlledOpen : internalOpen;
6481
6636
  const handleOpenChange = (_, data) => {
@@ -6719,7 +6874,7 @@ const InputAlphaField = (props) => {
6719
6874
  } }));
6720
6875
  };
6721
6876
 
6722
- const useStyles$P = makeStyles({
6877
+ const useStyles$Q = makeStyles({
6723
6878
  sidebar: {
6724
6879
  display: "flex",
6725
6880
  flexDirection: "column",
@@ -6783,7 +6938,7 @@ const useStyles$P = makeStyles({
6783
6938
  });
6784
6939
  const PerformanceSidebar = (props) => {
6785
6940
  const { collector, onVisibleRangeChangedObservable } = props;
6786
- const classes = useStyles$P();
6941
+ const classes = useStyles$Q();
6787
6942
  // Map from id to IPerfMetadata information
6788
6943
  const [metadataMap, setMetadataMap] = useState();
6789
6944
  // Map from category to all the ids belonging to that category
@@ -6856,7 +7011,7 @@ const PerformanceSidebar = (props) => {
6856
7011
  })] }, `category-${category || "version"}`))) }));
6857
7012
  };
6858
7013
 
6859
- const useStyles$O = makeStyles({
7014
+ const useStyles$P = makeStyles({
6860
7015
  container: {
6861
7016
  display: "flex",
6862
7017
  flexDirection: "row",
@@ -6885,7 +7040,7 @@ const useStyles$O = makeStyles({
6885
7040
  });
6886
7041
  const PerformanceViewer = (props) => {
6887
7042
  const { scene, layoutObservable, returnToLiveObservable, performanceCollector, initialGraphSize } = props;
6888
- const classes = useStyles$O();
7043
+ const classes = useStyles$P();
6889
7044
  const [onVisibleRangeChangedObservable] = useState(() => new Observable());
6890
7045
  const onReturnToPlayheadClick = () => {
6891
7046
  returnToLiveObservable.notifyObservers();
@@ -7052,14 +7207,14 @@ const TextPropertyLine = (props) => {
7052
7207
  return (jsx(PropertyLine, { ...props, children: jsx(Body1, { title: title, children: value ?? "" }) }));
7053
7208
  };
7054
7209
 
7055
- const useStyles$N = makeStyles({
7210
+ const useStyles$O = makeStyles({
7056
7211
  pinnedStatsPane: {
7057
7212
  flex: "0 1 auto",
7058
7213
  paddingBottom: tokens.spacingHorizontalM,
7059
7214
  },
7060
7215
  });
7061
7216
  const StatsPane = (props) => {
7062
- const classes = useStyles$N();
7217
+ const classes = useStyles$O();
7063
7218
  const scene = props.context;
7064
7219
  const engine = scene.getEngine();
7065
7220
  const pollingObservable = usePollingObservable(250);
@@ -7222,7 +7377,7 @@ const ToolsServiceDefinition = {
7222
7377
  */
7223
7378
  const ReactContextServiceIdentity = Symbol("ReactContextService");
7224
7379
 
7225
- const useStyles$M = makeStyles({
7380
+ const useStyles$N = makeStyles({
7226
7381
  dropdown: {
7227
7382
  ...UniformWidthStyling,
7228
7383
  },
@@ -7234,7 +7389,7 @@ const useStyles$M = makeStyles({
7234
7389
  */
7235
7390
  const DropdownPropertyLine = forwardRef((props, ref) => {
7236
7391
  DropdownPropertyLine.displayName = "DropdownPropertyLine";
7237
- const classes = useStyles$M();
7392
+ const classes = useStyles$N();
7238
7393
  return (jsx(PropertyLine, { ...props, ref: ref, children: jsx(Dropdown, { ...props, className: classes.dropdown }) }));
7239
7394
  });
7240
7395
  /**
@@ -7392,7 +7547,7 @@ const SyncedSliderInput = (props) => {
7392
7547
  return (jsxs("div", { className: mergeClasses(classes.container, props.className), 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: useCompactSizing ? classes.compactSpinButton : classes.spinButton, inputClassName: useCompactSizing ? classes.compactSpinButtonInput : classes.spinButtonInput, value: value, onChange: handleInputChange, step: props.step, disabled: props.disabled, disableDragButton: true })] })] }));
7393
7548
  };
7394
7549
 
7395
- const useStyles$L = makeStyles({
7550
+ const useStyles$M = makeStyles({
7396
7551
  uniformWidth: {
7397
7552
  ...UniformWidthStyling,
7398
7553
  },
@@ -7404,7 +7559,7 @@ const useStyles$L = makeStyles({
7404
7559
  */
7405
7560
  const SyncedSliderPropertyLine = forwardRef((props, ref) => {
7406
7561
  SyncedSliderPropertyLine.displayName = "SyncedSliderPropertyLine";
7407
- const classes = useStyles$L();
7562
+ const classes = useStyles$M();
7408
7563
  const { label, description, ...sliderProps } = props;
7409
7564
  return (jsx(PropertyLine, { ref: ref, ...props, children: jsx(SyncedSliderInput, { ...sliderProps, className: mergeClasses(classes.uniformWidth, props.className) }) }));
7410
7565
  });
@@ -7647,6 +7802,48 @@ const GizmoServiceDefinition = {
7647
7802
  const getCameraGizmo = (camera) => getGizmo(camera, camera.getScene(), CameraGizmo, cameraGizmos, (camera, gizmo) => (gizmo.camera = camera));
7648
7803
  const lightGizmos = new WeakMap();
7649
7804
  const getLightGizmo = (light) => getGizmo(light, light.getScene(), LightGizmo, lightGizmos, (light, gizmo) => (gizmo.light = light));
7805
+ // Ref-counted spatial audio gizmos. Sound sources are not Babylon Nodes, so we can't reuse the helper above directly.
7806
+ // Unlike `getGizmo`, this doesn't override `gizmo.dispose` — it routes shared cleanup through a lambda
7807
+ // (avoiding both Function.bind and Function.call, which are prohibited by the repo's performance/style rules).
7808
+ const spatialAudioGizmos = new WeakMap();
7809
+ const getSpatialAudioGizmo = (soundSource, scene) => {
7810
+ let refCounted = spatialAudioGizmos.get(soundSource);
7811
+ if (!refCounted) {
7812
+ const utilityLayerRef = getUtilityLayer(scene);
7813
+ const gizmo = new SpatialAudioGizmo(utilityLayerRef.value);
7814
+ gizmo.soundSource = soundSource;
7815
+ let isCleanedUp = false;
7816
+ const cleanup = () => {
7817
+ if (isCleanedUp) {
7818
+ return;
7819
+ }
7820
+ isCleanedUp = true;
7821
+ sourceDisposedObserver.remove();
7822
+ gizmo.dispose();
7823
+ utilityLayerRef.dispose();
7824
+ spatialAudioGizmos.delete(soundSource);
7825
+ };
7826
+ // If the underlying sound source is disposed externally, tear the gizmo down too.
7827
+ const sourceDisposedObserver = soundSource.onDisposeObservable.addOnce(cleanup);
7828
+ refCounted = { gizmo, cleanup, refCount: 0 };
7829
+ spatialAudioGizmos.set(soundSource, refCounted);
7830
+ }
7831
+ refCounted.refCount++;
7832
+ const refCountedCapture = refCounted;
7833
+ let disposed = false;
7834
+ return {
7835
+ value: refCounted.gizmo,
7836
+ dispose: () => {
7837
+ if (!disposed) {
7838
+ disposed = true;
7839
+ refCountedCapture.refCount--;
7840
+ if (refCountedCapture.refCount === 0) {
7841
+ refCountedCapture.cleanup();
7842
+ }
7843
+ }
7844
+ },
7845
+ };
7846
+ };
7650
7847
  // Gizmo mode/coordinates state and GizmoManager lifecycle.
7651
7848
  let gizmoModeState = undefined;
7652
7849
  const gizmoModeObservable = new Observable();
@@ -7782,6 +7979,7 @@ const GizmoServiceDefinition = {
7782
7979
  getUtilityLayer,
7783
7980
  getCameraGizmo,
7784
7981
  getLightGizmo,
7982
+ getSpatialAudioGizmo,
7785
7983
  getCameraGizmos: (scene) => scene.cameras.map((camera) => cameraGizmos.get(camera)?.gizmo).filter(Boolean),
7786
7984
  getLightGizmos: (scene) => scene.lights.map((light) => lightGizmos.get(light)?.gizmo).filter(Boolean),
7787
7985
  get gizmoMode() {
@@ -8354,17 +8552,24 @@ class ServiceContainer {
8354
8552
  }
8355
8553
  }
8356
8554
 
8555
+ /**
8556
+ * The unique identity symbol for the dialog service.
8557
+ */
8558
+ const DialogServiceIdentity = Symbol("DialogService");
8559
+
8357
8560
  /**
8358
8561
  * The unique identity symbol for the toast service.
8359
8562
  */
8360
8563
  const ToastServiceIdentity = Symbol("ToastService");
8361
8564
 
8565
+ const DialogContext = createContext({ showDialog: (options) => alert(options.title) });
8566
+
8362
8567
  const ExtensionManagerContext = createContext(undefined);
8363
8568
  function useExtensionManager() {
8364
8569
  return useContext(ExtensionManagerContext)?.extensionManager;
8365
8570
  }
8366
8571
 
8367
- const useStyles$K = makeStyles({
8572
+ const useStyles$L = makeStyles({
8368
8573
  themeButton: {
8369
8574
  margin: 0,
8370
8575
  },
@@ -8383,7 +8588,7 @@ const ThemeSelectorServiceDefinition = {
8383
8588
  teachingMoment: false,
8384
8589
  order: -300,
8385
8590
  component: () => {
8386
- const classes = useStyles$K();
8591
+ const classes = useStyles$L();
8387
8592
  const { isDarkMode, themeMode, setThemeMode } = useThemeMode();
8388
8593
  const onSelectedThemeChange = useCallback((e, data) => {
8389
8594
  setThemeMode(data.checkedItems.includes("System") ? "system" : data.checkedItems[0].toLocaleLowerCase());
@@ -8400,7 +8605,7 @@ const ThemeSelectorServiceDefinition = {
8400
8605
  },
8401
8606
  };
8402
8607
 
8403
- const useStyles$J = makeStyles({
8608
+ const useStyles$K = makeStyles({
8404
8609
  app: {
8405
8610
  colorScheme: "light dark",
8406
8611
  flexGrow: 1,
@@ -8425,6 +8630,24 @@ const useStyles$J = makeStyles({
8425
8630
  extensionErrorIcon: {
8426
8631
  color: tokens.colorPaletteRedForeground1,
8427
8632
  },
8633
+ dialogTitle: {
8634
+ display: "flex",
8635
+ flexDirection: "row",
8636
+ alignItems: "center",
8637
+ gap: tokens.spacingHorizontalS,
8638
+ },
8639
+ dialogIconSuccess: {
8640
+ color: tokens.colorStatusSuccessForeground1,
8641
+ },
8642
+ dialogIconError: {
8643
+ color: tokens.colorStatusDangerForeground1,
8644
+ },
8645
+ dialogIconWarning: {
8646
+ color: tokens.colorStatusWarningForeground1,
8647
+ },
8648
+ dialogIconInfo: {
8649
+ color: tokens.colorBrandForeground1,
8650
+ },
8428
8651
  });
8429
8652
  const ReactContextsWrapper = ({ contexts, children }) => {
8430
8653
  return jsx(Fragment, { children: contexts.reduceRight((acc, entry) => createElement(entry.provider, { value: entry.value }, acc), children) });
@@ -8445,7 +8668,7 @@ function MakeModularTool(options) {
8445
8668
  // This deferred resolves once the React effect cleanup (which disposes the ServiceContainer) is complete.
8446
8669
  const disposeDeferred = new Deferred();
8447
8670
  const modularToolRootComponent = () => {
8448
- const classes = useStyles$J();
8671
+ const classes = useStyles$K();
8449
8672
  const [extensionManagerContext, setExtensionManagerContext] = useState();
8450
8673
  const [requiredExtensions, setRequiredExtensions] = useState();
8451
8674
  const [requiredExtensionsDeferred, setRequiredExtensionsDeferred] = useState();
@@ -8460,6 +8683,21 @@ function MakeModularTool(options) {
8460
8683
  setToastQueue([]);
8461
8684
  }
8462
8685
  }, [toastHandle, toastQueue]);
8686
+ // Queue of dialogs to display. We show one at a time (the one at the head of the queue).
8687
+ const [dialogQueue, dispatchDialogQueue] = useReducer((state, action) => {
8688
+ switch (action.type) {
8689
+ case "enqueue":
8690
+ return [...state, action.options];
8691
+ case "dequeue":
8692
+ return state.slice(1);
8693
+ }
8694
+ }, []);
8695
+ const showDialog = useCallback((dialogOptions) => {
8696
+ dispatchDialogQueue({ type: "enqueue", options: dialogOptions });
8697
+ }, []);
8698
+ const onDismissDialog = useCallback(() => {
8699
+ dispatchDialogQueue({ type: "dequeue" });
8700
+ }, []);
8463
8701
  const [rootComponentService, setRootComponentService] = useState();
8464
8702
  const [contexts, updateContexts] = useReducer((state, action) => {
8465
8703
  switch (action.type) {
@@ -8510,6 +8748,12 @@ function MakeModularTool(options) {
8510
8748
  },
8511
8749
  }),
8512
8750
  });
8751
+ // Expose the dialog service so non-React code (e.g. Observable callbacks) can show dialogs.
8752
+ serviceContainer.addService({
8753
+ friendlyName: "Dialog Service",
8754
+ produces: [DialogServiceIdentity],
8755
+ factory: () => ({ showDialog }),
8756
+ });
8513
8757
  // Register the shell service (top level toolbar/side pane UI layout).
8514
8758
  serviceContainer.addService(MakeShellServiceDefinition(options));
8515
8759
  // Register a service that simply consumes the services we need before first render.
@@ -8532,7 +8776,7 @@ function MakeModularTool(options) {
8532
8776
  }
8533
8777
  // Register the extension list service (for browsing/installing extensions) if extension feeds are provided.
8534
8778
  if (extensionFeeds.length > 0) {
8535
- const { ExtensionListServiceDefinition } = await import('./extensionsListService-Drj94Pma.js');
8779
+ const { ExtensionListServiceDefinition } = await import('./extensionsListService-j6viqje8.js');
8536
8780
  serviceContainer.addService(ExtensionListServiceDefinition);
8537
8781
  }
8538
8782
  // Register all external services (that make up a unique tool).
@@ -8599,16 +8843,36 @@ function MakeModularTool(options) {
8599
8843
  const onAcknowledgedExtensionInstallError = useCallback(() => {
8600
8844
  setExtensionInstallError(undefined);
8601
8845
  }, [setExtensionInstallError]);
8846
+ // The dialog at the head of the queue, if any, is the one currently being displayed.
8847
+ const currentDialog = dialogQueue[0];
8848
+ const currentDialogIcon = (() => {
8849
+ switch (currentDialog?.intent) {
8850
+ case "success":
8851
+ return jsx(CheckmarkCircleRegular, { className: classes.dialogIconSuccess });
8852
+ case "error":
8853
+ return jsx(ErrorCircleRegular, { className: classes.dialogIconError });
8854
+ case "warning":
8855
+ return jsx(WarningRegular, { className: classes.dialogIconWarning });
8856
+ case "info":
8857
+ case undefined:
8858
+ return jsx(InfoRegular, { className: classes.dialogIconInfo });
8859
+ }
8860
+ })();
8602
8861
  // Show a spinner until a main view has been set.
8603
8862
  if (!rootComponentService) {
8604
- return (jsx(ReactContextsWrapper, { contexts: contexts, children: jsx(SettingsStoreContext.Provider, { value: settingsStore, children: jsx(Theme, { className: classes.app, children: jsx(Spinner, { className: classes.spinner }) }) }) }));
8863
+ return (jsx(ReactContextsWrapper, { contexts: contexts, children: jsx(SettingsStoreContext.Provider, { value: settingsStore, children: jsx(Theme, { className: classes.app, targetDocument: targetDocument, children: jsx(Spinner, { className: classes.spinner }) }) }) }));
8605
8864
  }
8606
8865
  else {
8607
8866
  // eslint-disable-next-line @typescript-eslint/naming-convention
8608
8867
  const Content = rootComponentService.rootComponent;
8609
- return (jsx(ReactContextsWrapper, { contexts: contexts, children: jsx(SettingsStoreContext.Provider, { value: settingsStore, children: jsx(ExtensionManagerContext.Provider, { value: extensionManagerContext, children: jsx(Theme, { className: classes.app, children: jsxs(ToastProvider, { imperativeRef: setToastHandle, children: [jsx(Dialog$1, { 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$1, { 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, {}) })] }) }) }) }) }));
8868
+ return (jsx(ReactContextsWrapper, { contexts: contexts, children: jsx(SettingsStoreContext.Provider, { value: settingsStore, children: jsx(ExtensionManagerContext.Provider, { value: extensionManagerContext, children: jsx(Theme, { className: classes.app, targetDocument: targetDocument, children: jsxs(ToastProvider, { imperativeRef: setToastHandle, children: [jsx(Dialog$1, { 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$1, { 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(Dialog$1, { open: !!currentDialog, modalType: "alert", children: jsx(DialogSurface, { children: jsxs(DialogBody, { children: [jsx(DialogTitle, { children: jsxs("div", { className: classes.dialogTitle, children: [currentDialogIcon, currentDialog?.title] }) }), currentDialog?.content && jsx(DialogContent, { children: currentDialog.content }), jsx(DialogActions, { children: jsx(Button$1, { appearance: "primary", onClick: onDismissDialog, children: "OK" }) })] }) }) }), jsx(Suspense, { fallback: jsx(Spinner, { className: classes.spinner }), children: jsx(DialogContext.Provider, { value: { showDialog }, children: jsx(Content, {}) }) })] }) }) }) }) }));
8610
8869
  }
8611
8870
  };
8871
+ // Derive the target document from the container element. When the container is in a popup
8872
+ // window (or other document distinct from the main one), this is what makes Fluent inject
8873
+ // styles and render portals into the correct document.
8874
+ const containerOwnerDocument = containerElement.ownerDocument;
8875
+ const targetDocument = containerOwnerDocument && containerOwnerDocument !== document ? containerOwnerDocument : undefined;
8612
8876
  // Set the container element to be a flex container so that the tool can be displayed properly.
8613
8877
  const originalContainerElementDisplay = containerElement.style.display;
8614
8878
  containerElement.style.display = "flex";
@@ -8653,14 +8917,14 @@ const DefaultInspectorExtensionFeed = new BuiltInsExtensionFeed("Inspector", [
8653
8917
  description: "Adds a new panel for easy creation of various Babylon assets. This is a WIP extension...expect changes!",
8654
8918
  keywords: ["creation", "tools"],
8655
8919
  ...BabylonWebResources,
8656
- getExtensionModuleAsync: async () => await import('./quickCreateToolsService-CXUBvaDf.js'),
8920
+ getExtensionModuleAsync: async () => await import('./quickCreateToolsService-eZ4MCuJ2.js'),
8657
8921
  },
8658
8922
  {
8659
8923
  name: "Reflector",
8660
8924
  description: "Connects to the Reflector Bridge for real-time scene synchronization with the Babylon.js Sandbox.",
8661
8925
  keywords: ["reflector", "bridge", "sync", "sandbox", "tools"],
8662
8926
  ...BabylonWebResources,
8663
- getExtensionModuleAsync: async () => await import('./reflectorService-D3JRLq4p.js'),
8927
+ getExtensionModuleAsync: async () => await import('./reflectorService-2dP-GJrK.js'),
8664
8928
  },
8665
8929
  ]);
8666
8930
 
@@ -9701,7 +9965,7 @@ const ColorSliders = ({ color, onSliderChange }) => (jsxs(Fragment, { children:
9701
9965
  const Color3PropertyLine = ColorPropertyLine;
9702
9966
  const Color4PropertyLine = ColorPropertyLine;
9703
9967
 
9704
- const useStyles$I = makeStyles({
9968
+ const useStyles$J = makeStyles({
9705
9969
  uniformWidth: {
9706
9970
  ...UniformWidthStyling,
9707
9971
  },
@@ -9713,7 +9977,7 @@ const useStyles$I = makeStyles({
9713
9977
  */
9714
9978
  const TextInputPropertyLine = (props) => {
9715
9979
  TextInputPropertyLine.displayName = "TextInputPropertyLine";
9716
- const classes = useStyles$I();
9980
+ const classes = useStyles$J();
9717
9981
  return (jsx(PropertyLine, { ...props, children: jsx(TextInput, { ...props, className: mergeClasses(classes.uniformWidth, props.className) }) }));
9718
9982
  };
9719
9983
  /**
@@ -9724,7 +9988,7 @@ const TextInputPropertyLine = (props) => {
9724
9988
  */
9725
9989
  const NumberInputPropertyLine = (props) => {
9726
9990
  NumberInputPropertyLine.displayName = "NumberInputPropertyLine";
9727
- const classes = useStyles$I();
9991
+ const classes = useStyles$J();
9728
9992
  return (jsx(PropertyLine, { ...props, children: jsx(SpinButton, { ...props, className: mergeClasses(classes.uniformWidth, props.className) }) }));
9729
9993
  };
9730
9994
 
@@ -9856,7 +10120,7 @@ const LegacyInspectableObjectPropertiesServiceDefinition = {
9856
10120
  };
9857
10121
 
9858
10122
  const DocUrl = "https://www.npmjs.com/package/@babylonjs/inspector#inspector-cli";
9859
- const useStyles$H = makeStyles({
10123
+ const useStyles$I = makeStyles({
9860
10124
  tooltipContent: {
9861
10125
  display: "flex",
9862
10126
  flexDirection: "column",
@@ -9874,7 +10138,7 @@ const CliConnectionStatusServiceDefinition = {
9874
10138
  teachingMoment: false,
9875
10139
  order: 0 /* DefaultToolbarItemOrder.CliStatus */,
9876
10140
  component: () => {
9877
- const classes = useStyles$H();
10141
+ const classes = useStyles$I();
9878
10142
  const isEnabled = useObservableState(() => cliConnectionStatus.isEnabled, cliConnectionStatus.onConnectionStatusChanged);
9879
10143
  const isConnected = useObservableState(() => cliConnectionStatus.isConnected, cliConnectionStatus.onConnectionStatusChanged);
9880
10144
  const { showToast } = useToast();
@@ -9935,7 +10199,7 @@ const BreakTangentIcon = createFluentIcon("BreakTangent", "20", '<g transform="s
9935
10199
  const UnifyTangentIcon = createFluentIcon("UnifyTangent", "20", '<g transform="scale(0.5)"><path d="M27.94,18.28a1.49,1.49,0,0,0-1.41,1h-5l-1.62-1.63-1.62,1.63h-5a1.5,1.5,0,1,0,0,1h5l1.62,1.62,1.62-1.62h5a1.5,1.5,0,1,0,1.41-2Z"/></g>');
9936
10200
  const StepTangentIcon = createFluentIcon("StepTangent", "20", '<g transform="scale(0.5)"><path d="M29,16.71a1.5,1.5,0,1,0-2,1.41v5.67H11v1H28V18.12A1.51,1.51,0,0,0,29,16.71Z"/></g>');
9937
10201
 
9938
- const useStyles$G = makeStyles({
10202
+ const useStyles$H = makeStyles({
9939
10203
  coordinatesModeButton: {
9940
10204
  margin: `0 0 0 ${tokens.spacingHorizontalXS}`,
9941
10205
  },
@@ -9951,7 +10215,7 @@ const useStyles$G = makeStyles({
9951
10215
  });
9952
10216
  const GizmoToolbar = (props) => {
9953
10217
  const { gizmoService, sceneContext } = props;
9954
- const classes = useStyles$G();
10218
+ const classes = useStyles$H();
9955
10219
  const gizmoMode = useObservableState(() => gizmoService.gizmoMode, gizmoService.onGizmoModeChanged);
9956
10220
  const coordinatesMode = useObservableState(() => gizmoService.coordinatesMode, gizmoService.onCoordinatesModeChanged);
9957
10221
  const cameraGizmo = useObservableState(() => gizmoService.gizmoCamera, gizmoService.onCameraGizmoChanged);
@@ -10080,7 +10344,7 @@ const HighlightServiceDefinition = {
10080
10344
  },
10081
10345
  };
10082
10346
 
10083
- const useStyles$F = makeStyles({
10347
+ const useStyles$G = makeStyles({
10084
10348
  badge: {
10085
10349
  margin: tokens.spacingHorizontalXXS,
10086
10350
  fontFamily: "monospace",
@@ -10097,7 +10361,7 @@ const MiniStatsServiceDefinition = {
10097
10361
  order: 300 /* DefaultToolbarItemOrder.FrameRate */,
10098
10362
  teachingMoment: false,
10099
10363
  component: () => {
10100
- const classes = useStyles$F();
10364
+ const classes = useStyles$G();
10101
10365
  const scene = useObservableState(useCallback(() => sceneContext.currentScene, [sceneContext.currentScene]), sceneContext.currentSceneObservable);
10102
10366
  const engine = scene?.getEngine();
10103
10367
  const pollingObservable = usePollingObservable(250);
@@ -10426,7 +10690,7 @@ function useCurveEditor() {
10426
10690
  return context;
10427
10691
  }
10428
10692
 
10429
- const useStyles$E = makeStyles({
10693
+ const useStyles$F = makeStyles({
10430
10694
  root: {
10431
10695
  display: "flex",
10432
10696
  flexDirection: "row",
@@ -10470,7 +10734,7 @@ const useStyles$E = makeStyles({
10470
10734
  * @returns The top bar component
10471
10735
  */
10472
10736
  const TopBar = () => {
10473
- const styles = useStyles$E();
10737
+ const styles = useStyles$F();
10474
10738
  const { state, observables } = useCurveEditor();
10475
10739
  const [keyFrameValue, setKeyFrameValue] = useState(null);
10476
10740
  const [keyValue, setKeyValue] = useState(null);
@@ -10563,7 +10827,7 @@ const ColorChannelColors = {
10563
10827
  */
10564
10828
  const DefaultCurveColor = "#ffffff";
10565
10829
 
10566
- const useStyles$D = makeStyles({
10830
+ const useStyles$E = makeStyles({
10567
10831
  root: {
10568
10832
  display: "flex",
10569
10833
  flexDirection: "column",
@@ -10605,7 +10869,7 @@ const LOOP_MODES$1 = [
10605
10869
  * @returns The edit animation panel component
10606
10870
  */
10607
10871
  const EditAnimationPanel = ({ animation, onClose }) => {
10608
- const styles = useStyles$D();
10872
+ const styles = useStyles$E();
10609
10873
  const { observables } = useCurveEditor();
10610
10874
  const [name, setName] = useState(animation.name);
10611
10875
  const [property, setProperty] = useState(animation.targetProperty);
@@ -10636,7 +10900,7 @@ const EditAnimationPanel = ({ animation, onClose }) => {
10636
10900
  return (jsxs("div", { className: styles.root, children: [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: "Property" }), jsx(Input, { value: property, onChange: (_, data) => setProperty(data.value), placeholder: "e.g., position, rotation, scaling" })] }), jsxs("div", { className: styles.row, children: [jsx(Label, { children: "Loop Mode" }), jsx(Dropdown$1, { value: getLoopModeLabel(loopMode), selectedOptions: [loopMode.toString()], onOptionSelect: (_, data) => setLoopMode(Number(data.optionValue)), positioning: "below", inlinePopup: true, children: LOOP_MODES$1.map((mode) => (jsx(Option, { value: mode.value.toString(), children: mode.label }, mode.value))) })] })] }), jsxs("div", { className: styles.buttons, children: [jsx(Button, { appearance: "primary", onClick: saveChanges, label: "Save", disabled: !isValid }), jsx(Button, { appearance: "subtle", onClick: onClose, label: "Cancel" })] })] }));
10637
10901
  };
10638
10902
 
10639
- const useStyles$C = makeStyles({
10903
+ const useStyles$D = makeStyles({
10640
10904
  root: {
10641
10905
  display: "flex",
10642
10906
  flexDirection: "column",
@@ -10702,7 +10966,7 @@ const useStyles$C = makeStyles({
10702
10966
  * @returns Animation entry component
10703
10967
  */
10704
10968
  const AnimationEntry = ({ animation }) => {
10705
- const styles = useStyles$C();
10969
+ const styles = useStyles$D();
10706
10970
  const { state, actions, observables } = useCurveEditor();
10707
10971
  const [isExpanded, setIsExpanded] = useState(false);
10708
10972
  const [isHovered, setIsHovered] = useState(false);
@@ -10781,7 +11045,7 @@ const AnimationEntry = ({ animation }) => {
10781
11045
  * @returns Animation sub-entry component
10782
11046
  */
10783
11047
  const AnimationSubEntry = ({ animation, subName, color }) => {
10784
- const styles = useStyles$C();
11048
+ const styles = useStyles$D();
10785
11049
  const { actions, observables } = useCurveEditor();
10786
11050
  const activeChannel = actions.getActiveChannel(animation);
10787
11051
  const isThisChannelActive = activeChannel === color;
@@ -10804,7 +11068,7 @@ const AnimationSubEntry = ({ animation, subName, color }) => {
10804
11068
  * @returns Animation list component
10805
11069
  */
10806
11070
  const AnimationList = () => {
10807
- const styles = useStyles$C();
11071
+ const styles = useStyles$D();
10808
11072
  const { state, observables } = useCurveEditor();
10809
11073
  // Re-render when animations are loaded or changed (e.g. animation deleted)
10810
11074
  // useCallback stabilizes the accessor to prevent infinite re-render loops
@@ -10817,7 +11081,7 @@ const AnimationList = () => {
10817
11081
  }) }));
10818
11082
  };
10819
11083
 
10820
- const useStyles$B = makeStyles({
11084
+ const useStyles$C = makeStyles({
10821
11085
  root: {
10822
11086
  display: "flex",
10823
11087
  flexDirection: "column",
@@ -10866,7 +11130,7 @@ const LoopModeOptions = LOOP_MODES.map((lm) => ({ label: lm, value: lm }));
10866
11130
  * @returns The add animation panel component
10867
11131
  */
10868
11132
  const AddAnimationPanel = ({ onClose }) => {
10869
- const styles = useStyles$B();
11133
+ const styles = useStyles$C();
10870
11134
  const { state, actions, observables } = useCurveEditor();
10871
11135
  const [name, setName] = useState("");
10872
11136
  const [mode, setMode] = useState("List");
@@ -11083,7 +11347,7 @@ const AddAnimationPanel = ({ onClose }) => {
11083
11347
  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" })] })] }));
11084
11348
  };
11085
11349
 
11086
- const useStyles$A = makeStyles({
11350
+ const useStyles$B = makeStyles({
11087
11351
  root: {
11088
11352
  display: "flex",
11089
11353
  flexDirection: "column",
@@ -11126,7 +11390,7 @@ const useStyles$A = makeStyles({
11126
11390
  * @returns The load animation panel component
11127
11391
  */
11128
11392
  const LoadAnimationPanel = ({ onClose }) => {
11129
- const styles = useStyles$A();
11393
+ const styles = useStyles$B();
11130
11394
  const { state, actions, observables } = useCurveEditor();
11131
11395
  const [snippetIdInput, setSnippetIdInput] = useState("");
11132
11396
  const [loadedSnippetId, setLoadedSnippetId] = useState(null);
@@ -11265,7 +11529,7 @@ class StringTools {
11265
11529
  }
11266
11530
  }
11267
11531
 
11268
- const useStyles$z = makeStyles({
11532
+ const useStyles$A = makeStyles({
11269
11533
  root: {
11270
11534
  display: "flex",
11271
11535
  flexDirection: "column",
@@ -11310,7 +11574,7 @@ const useStyles$z = makeStyles({
11310
11574
  * @returns The save animation panel component
11311
11575
  */
11312
11576
  const SaveAnimationPanel = ({ onClose: _onClose }) => {
11313
- const styles = useStyles$z();
11577
+ const styles = useStyles$A();
11314
11578
  const { state } = useCurveEditor();
11315
11579
  const [selectedAnimations, setSelectedAnimations] = useState(() => {
11316
11580
  if (!state.animations) {
@@ -11383,7 +11647,7 @@ const SaveAnimationPanel = ({ onClose: _onClose }) => {
11383
11647
  }) }), jsxs("div", { className: styles.buttons, children: [jsx(Button, { appearance: "primary", onClick: saveToSnippetServer, disabled: selectedAnimations.length === 0 || isSaving, label: isSaving ? "Saving..." : "Save to Snippet Server" }), jsx(Button, { appearance: "secondary", onClick: saveToFile, disabled: selectedAnimations.length === 0, label: "Save to File" })] }), saveError && jsx("div", { className: styles.errorText, children: saveError }), snippetId && jsxs("div", { className: styles.snippetId, children: ["Saved! Snippet ID: ", snippetId] })] }));
11384
11648
  };
11385
11649
 
11386
- const useStyles$y = makeStyles({
11650
+ const useStyles$z = makeStyles({
11387
11651
  root: {
11388
11652
  display: "flex",
11389
11653
  flexDirection: "column",
@@ -11437,7 +11701,7 @@ const useStyles$y = makeStyles({
11437
11701
  * @returns The sidebar component
11438
11702
  */
11439
11703
  const SideBar = () => {
11440
- const styles = useStyles$y();
11704
+ const styles = useStyles$z();
11441
11705
  const { state, actions, observables } = useCurveEditor();
11442
11706
  const [openPopover, setOpenPopover] = useState(null);
11443
11707
  const [fps, setFps] = useState(60);
@@ -12425,7 +12689,7 @@ const KeyPointComponent = (props) => {
12425
12689
  } })] }))] }))] }));
12426
12690
  };
12427
12691
 
12428
- const useStyles$x = makeStyles({
12692
+ const useStyles$y = makeStyles({
12429
12693
  root: {
12430
12694
  position: "absolute",
12431
12695
  top: 0,
@@ -12609,7 +12873,7 @@ function ExtractValuesFromKeys(keys, curves) {
12609
12873
  * @returns The graph component
12610
12874
  */
12611
12875
  const Graph = ({ width, height }) => {
12612
- const styles = useStyles$x();
12876
+ const styles = useStyles$y();
12613
12877
  const { state, actions, observables } = useCurveEditor();
12614
12878
  const svgRef = useRef(null);
12615
12879
  const [scale, setScale] = useState(1);
@@ -12972,7 +13236,7 @@ const Graph = ({ width, height }) => {
12972
13236
  }), renderValueAxis()] })] }));
12973
13237
  };
12974
13238
 
12975
- const useStyles$w = makeStyles({
13239
+ const useStyles$x = makeStyles({
12976
13240
  root: {
12977
13241
  position: "absolute",
12978
13242
  top: 0,
@@ -13015,7 +13279,7 @@ const useStyles$w = makeStyles({
13015
13279
  * @returns The playhead component
13016
13280
  */
13017
13281
  const PlayHead = ({ width, height: _height }) => {
13018
- const styles = useStyles$w();
13282
+ const styles = useStyles$x();
13019
13283
  const { state, actions, observables } = useCurveEditor();
13020
13284
  const [isDragging, setIsDragging] = useState(false);
13021
13285
  // Use refs for all mutable values to avoid render cycles
@@ -13166,7 +13430,7 @@ const PlayHead = ({ width, height: _height }) => {
13166
13430
  return (jsxs("div", { className: styles.root, children: [jsx("div", { ref: lineRef, className: styles.line, onPointerDown: handlePointerDown, onPointerMove: handlePointerMove, onPointerUp: handlePointerUp }), jsx("div", { ref: handleRef, className: styles.handle, onPointerDown: handlePointerDown, onPointerMove: handlePointerMove, onPointerUp: handlePointerUp })] }));
13167
13431
  };
13168
13432
 
13169
- const useStyles$v = makeStyles({
13433
+ const useStyles$w = makeStyles({
13170
13434
  root: {
13171
13435
  display: "flex",
13172
13436
  flexDirection: "row",
@@ -13198,7 +13462,7 @@ const useStyles$v = makeStyles({
13198
13462
  * @returns The frame bar component
13199
13463
  */
13200
13464
  const FrameBar = ({ width }) => {
13201
- const styles = useStyles$v();
13465
+ const styles = useStyles$w();
13202
13466
  const { state, observables } = useCurveEditor();
13203
13467
  const containerRef = useRef(null);
13204
13468
  const [scale, setScale] = useState(1);
@@ -13256,7 +13520,7 @@ const FrameBar = ({ width }) => {
13256
13520
  return (jsx("div", { className: styles.root, ref: containerRef, children: renderTicks() }));
13257
13521
  };
13258
13522
 
13259
- const useStyles$u = makeStyles({
13523
+ const useStyles$v = makeStyles({
13260
13524
  root: {
13261
13525
  display: "flex",
13262
13526
  flexDirection: "column",
@@ -13297,7 +13561,7 @@ const OFFSET_X = 10;
13297
13561
  * @returns The range frame bar component
13298
13562
  */
13299
13563
  const RangeFrameBar = ({ width }) => {
13300
- const styles = useStyles$u();
13564
+ const styles = useStyles$v();
13301
13565
  const { state, actions, observables } = useCurveEditor();
13302
13566
  const svgRef = useRef(null);
13303
13567
  const [viewWidth, setViewWidth] = useState(width);
@@ -13408,7 +13672,7 @@ const RangeFrameBar = ({ width }) => {
13408
13672
  }), renderKeyframes, renderActiveFrame] }) }));
13409
13673
  };
13410
13674
 
13411
- const useStyles$t = makeStyles({
13675
+ const useStyles$u = makeStyles({
13412
13676
  root: {
13413
13677
  display: "flex",
13414
13678
  flexDirection: "column",
@@ -13440,7 +13704,7 @@ const useStyles$t = makeStyles({
13440
13704
  * @returns The canvas component
13441
13705
  */
13442
13706
  const Canvas = () => {
13443
- const styles = useStyles$t();
13707
+ const styles = useStyles$u();
13444
13708
  const { observables } = useCurveEditor();
13445
13709
  const containerRef = useRef(null);
13446
13710
  const [dimensions, setDimensions] = useState({ width: 0, height: 0 });
@@ -13470,7 +13734,7 @@ const Canvas = () => {
13470
13734
  return (jsxs("div", { className: styles.root, ref: containerRef, children: [jsx("div", { className: styles.frameBar, children: jsx(FrameBar, { width: dimensions.width }) }), jsxs("div", { className: styles.canvasArea, children: [jsx(Graph, { width: dimensions.width, height: dimensions.height - 70 }), jsx(PlayHead, { width: dimensions.width, height: dimensions.height - 70 })] }), jsx("div", { className: styles.rangeFrameBar, children: jsx(RangeFrameBar, { width: dimensions.width }) })] }));
13471
13735
  };
13472
13736
 
13473
- const useStyles$s = makeStyles({
13737
+ const useStyles$t = makeStyles({
13474
13738
  root: {
13475
13739
  flex: 1,
13476
13740
  height: "25px",
@@ -13532,7 +13796,7 @@ const useStyles$s = makeStyles({
13532
13796
  * @returns The range selector component
13533
13797
  */
13534
13798
  const RangeSelector = () => {
13535
- const styles = useStyles$s();
13799
+ const styles = useStyles$t();
13536
13800
  const { state, actions, observables } = useCurveEditor();
13537
13801
  const containerRef = useRef(null);
13538
13802
  const scrollbarRef = useRef(null);
@@ -13677,7 +13941,7 @@ function GetKeyAtAnyFrameIndex(animations, frame) {
13677
13941
  }
13678
13942
  return false;
13679
13943
  }
13680
- const useStyles$r = makeStyles({
13944
+ const useStyles$s = makeStyles({
13681
13945
  root: {
13682
13946
  display: "flex",
13683
13947
  flexDirection: "row",
@@ -13734,7 +13998,7 @@ MediaControls.displayName = "MediaControls";
13734
13998
  * @returns The BottomBar component.
13735
13999
  */
13736
14000
  const BottomBar = () => {
13737
- const styles = useStyles$r();
14001
+ const styles = useStyles$s();
13738
14002
  const { state, actions, observables } = useCurveEditor();
13739
14003
  // Track display frame separately for smooth updates during playback
13740
14004
  const [displayFrame, setDisplayFrame] = useState(state.activeFrame);
@@ -13849,7 +14113,7 @@ const BottomBar = () => {
13849
14113
  return (jsxs("div", { className: styles.root, children: [jsx("div", { className: styles.mediaControls, children: jsx(MediaControls, { hasActiveAnimations: hasActiveAnimations, isPlaying: state.isPlaying, forwardAnimation: state.forwardAnimation, onPlayForward: handlePlayForward, onPlayBackward: handlePlayBackward, onStop: handleStop, onPrevKey: handlePrevKey, onNextKey: handleNextKey, onFirstFrame: handleFirstFrame, onLastFrame: handleLastFrame }) }), jsxs("div", { className: styles.frameDisplay, children: [jsx("div", { className: styles.frameLabel, children: "Frame:" }), jsx(SpinButton, { className: styles.spinButton, value: displayFrame, onChange: handleFrameChange, min: state.fromKey, max: state.toKey, disabled: !hasActiveAnimations })] }), jsx(RangeSelector, {}), jsxs("div", { className: styles.clipLengthSection, children: [jsx("div", { className: styles.frameLabel, children: "Clip Length:" }), jsx(SpinButton, { className: styles.spinButton, value: clipLength, onChange: handleClipLengthChange, min: 1, disabled: !hasActiveAnimations })] })] }));
13850
14114
  };
13851
14115
 
13852
- const useStyles$q = makeStyles({
14116
+ const useStyles$r = makeStyles({
13853
14117
  root: {
13854
14118
  display: "flex",
13855
14119
  flexDirection: "column",
@@ -13898,7 +14162,7 @@ const useStyles$q = makeStyles({
13898
14162
  * @returns The curve editor content
13899
14163
  */
13900
14164
  const CurveEditorContent = () => {
13901
- const styles = useStyles$q();
14165
+ const styles = useStyles$r();
13902
14166
  const { state, actions, observables } = useCurveEditor();
13903
14167
  const rootRef = useRef(null);
13904
14168
  const prepareRef = useRef(() => actions.prepare());
@@ -14255,7 +14519,7 @@ const AtmospherePropertiesServiceDefinition = {
14255
14519
  },
14256
14520
  };
14257
14521
 
14258
- function useSoundState(sound) {
14522
+ function useSoundState$1(sound) {
14259
14523
  const stateChangedObservables = [
14260
14524
  useInterceptObservable("function", sound, "play"),
14261
14525
  useInterceptObservable("function", sound, "pause"),
@@ -14267,12 +14531,12 @@ function useSoundState(sound) {
14267
14531
  }
14268
14532
  const SoundGeneralProperties = (props) => {
14269
14533
  const { sound } = props;
14270
- const soundState = useSoundState(sound);
14534
+ const soundState = useSoundState$1(sound);
14271
14535
  return (jsx(Fragment, { children: jsx(TextPropertyLine, { label: "Status", value: soundState }) }));
14272
14536
  };
14273
14537
  const SoundCommandProperties = (props) => {
14274
14538
  const { sound } = props;
14275
- const soundState = useSoundState(sound);
14539
+ const soundState = useSoundState$1(sound);
14276
14540
  const volume = useObservableState(useCallback(() => sound.getVolume(), [sound]), useInterceptObservable("function", sound, "setVolume"));
14277
14541
  return (jsxs(Fragment, { children: [jsx(ButtonLine, { uniqueId: "Start/Stop", label: soundState === "Playing" ? "Pause" : "Play", icon: soundState === "Playing" ? PauseRegular : PlayRegular, onClick: () => {
14278
14542
  if (soundState === "Playing") {
@@ -14286,11 +14550,196 @@ const SoundCommandProperties = (props) => {
14286
14550
  } }), jsx(BoundProperty, { component: SwitchPropertyLine, label: "Loop", target: sound, propertyKey: "loop" })] }));
14287
14551
  };
14288
14552
 
14553
+ /**
14554
+ * Spatial-audio property line that shows the scene node a v2 sound or sound source is attached to,
14555
+ * with a clickable link to navigate to that node in the inspector.
14556
+ * @returns The rendered property line.
14557
+ */
14558
+ const AudioV2SpatialAttachmentProperties = ({ source, selectionService }) => {
14559
+ // Intercept attach/detach on this instance's spatial sub-property so the link refreshes when attachment changes.
14560
+ const onAttach = useInterceptObservable("function", source.spatial, "attach");
14561
+ const onDetach = useInterceptObservable("function", source.spatial, "detach");
14562
+ const attachedNode = useObservableState(() => source.spatial.attachedNode, onAttach, onDetach);
14563
+ return (jsx(LinkToEntityPropertyLine, { label: "Attached Node", description: "The scene node this sound is attached to via spatial audio.", entity: attachedNode, selectionService: selectionService }));
14564
+ };
14565
+
14566
+ /**
14567
+ * Renders a clickable "Output Bus" property line for any audio entity that routes to a downstream bus.
14568
+ * @returns The rendered property line.
14569
+ */
14570
+ const AudioV2OutputBusLink = ({ target, description, selectionService }) => {
14571
+ const outBus = useObservableState(useCallback(() => target.outBus, [target]), useInterceptObservable("property", target, "outBus"));
14572
+ return (jsx(LinkToEntityPropertyLine, { label: "Output Bus", description: description ?? "The bus this entity routes its output to.", entity: outBus, selectionService: selectionService }));
14573
+ };
14574
+ // -----------------------------------------------------------------------------
14575
+ // Engine
14576
+ // -----------------------------------------------------------------------------
14577
+ function useEngineState(engine) {
14578
+ const stateChangedObservables = [
14579
+ useInterceptObservable("function", engine, "pauseAsync"),
14580
+ useInterceptObservable("function", engine, "resumeAsync"),
14581
+ useInterceptObservable("function", engine, "unlockAsync"),
14582
+ ];
14583
+ return useObservableState(useCallback(() => engine.state, [engine]), ...stateChangedObservables);
14584
+ }
14585
+ /**
14586
+ * Setup / playback properties for an {@link AudioEngineV2}.
14587
+ * @returns The rendered component.
14588
+ */
14589
+ const AudioV2EngineGeneralProperties = ({ engine }) => {
14590
+ const state = useEngineState(engine);
14591
+ const volume = useObservableState(useCallback(() => engine.volume, [engine]), useInterceptObservable("function", engine, "setVolume"));
14592
+ return (jsxs(Fragment, { children: [jsx(TextPropertyLine, { label: "State", value: state }), jsx(Property, { component: SyncedSliderPropertyLine, label: "Volume", functionPath: "setVolume", value: volume, min: 0, max: 1, step: 0.01, onChange: (value) => engine.setVolume(value) }), jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Parameter Ramp Duration", target: engine, propertyKey: "parameterRampDuration", min: 0, step: 0.01, unit: "s" })] }));
14593
+ };
14594
+ /**
14595
+ * Resume / pause controls for an {@link AudioEngineV2}.
14596
+ *
14597
+ * No separate Unlock button — `engine.unlockAsync()` is just a thin wrapper around
14598
+ * `engine.resumeAsync()` on the base class, so Resume already covers the autoplay-blocked path.
14599
+ * @returns The rendered component.
14600
+ */
14601
+ const AudioV2EngineCommandsProperties = ({ engine }) => {
14602
+ const state = useEngineState(engine);
14603
+ return (jsx(Fragment, { children: state === "running" ? (jsx(ButtonLine, { uniqueId: "audiov2-engine-pause", label: "Pause", icon: PauseRegular, onClick: () => void engine.pauseAsync() })) : (jsx(ButtonLine, { uniqueId: "audiov2-engine-resume", label: "Resume", icon: PlayRegular, onClick: () => void engine.resumeAsync() })) }));
14604
+ };
14605
+ /**
14606
+ * Listener spatial-attachment property line for an {@link AudioEngineV2}.
14607
+ * @returns The rendered component.
14608
+ */
14609
+ const AudioV2EngineListenerProperties = ({ engine, selectionService }) => {
14610
+ const listener = engine.listener;
14611
+ const onAttach = useInterceptObservable("function", listener, "attach");
14612
+ const onDetach = useInterceptObservable("function", listener, "detach");
14613
+ const attachedNode = useObservableState(() => listener.attachedNode, onAttach, onDetach);
14614
+ return (jsx(LinkToEntityPropertyLine, { label: "Attached Node", description: "The scene node the audio listener is attached to via spatial audio.", entity: attachedNode, selectionService: selectionService }));
14615
+ };
14616
+ // -----------------------------------------------------------------------------
14617
+ // Buses (Main + Audio)
14618
+ // -----------------------------------------------------------------------------
14619
+ /**
14620
+ * General properties shared by all v2 audio buses (main and regular).
14621
+ * @returns The rendered component.
14622
+ */
14623
+ const AudioV2BusGeneralProperties = ({ bus }) => {
14624
+ const volume = useObservableState(useCallback(() => bus.volume, [bus]), useInterceptObservable("function", bus, "setVolume"));
14625
+ return (jsx(Property, { component: SyncedSliderPropertyLine, label: "Volume", functionPath: "setVolume", value: volume, min: 0, max: 1, step: 0.01, onChange: (value) => bus.setVolume(value) }));
14626
+ };
14627
+ /**
14628
+ * Additional properties specific to {@link AudioBus} (a non-main bus that can be routed to another bus).
14629
+ * @returns The rendered component.
14630
+ */
14631
+ const AudioV2AudioBusGeneralProperties = ({ bus, selectionService }) => {
14632
+ return jsx(AudioV2OutputBusLink, { target: bus, description: "The bus this bus routes its output to.", selectionService: selectionService });
14633
+ };
14634
+ // -----------------------------------------------------------------------------
14635
+ // Sounds (StaticSound + StreamingSound)
14636
+ // -----------------------------------------------------------------------------
14637
+ function GetSoundStateLabel(state) {
14638
+ switch (state) {
14639
+ case 3 /* SoundState.Started */:
14640
+ return "Playing";
14641
+ case 5 /* SoundState.Paused */:
14642
+ return "Paused";
14643
+ case 2 /* SoundState.Starting */:
14644
+ return "Starting";
14645
+ case 0 /* SoundState.Stopping */:
14646
+ return "Stopping";
14647
+ case 4 /* SoundState.FailedToStart */:
14648
+ return "Failed to start";
14649
+ case 1 /* SoundState.Stopped */:
14650
+ default:
14651
+ return "Stopped";
14652
+ }
14653
+ }
14654
+ function useSoundState(sound) {
14655
+ const stateChangedObservables = [
14656
+ useInterceptObservable("function", sound, "play"),
14657
+ useInterceptObservable("function", sound, "pause"),
14658
+ useInterceptObservable("function", sound, "resume"),
14659
+ useInterceptObservable("function", sound, "stop"),
14660
+ // Engine-level hook fires for every state transition on every sound (including async
14661
+ // Starting → Started, FailedToStart, and natural Stopped after stop()), covering changes
14662
+ // that the per-method intercepts above miss.
14663
+ useInterceptObservable("function", sound.engine, "_onSoundPlaybackStateChanged"),
14664
+ ];
14665
+ return useObservableState(useCallback(() => sound.state, [sound]), ...stateChangedObservables,
14666
+ // Fires when the sound ends naturally (full duration, non-looping).
14667
+ sound.onEndedObservable);
14668
+ }
14669
+ /**
14670
+ * General properties for any v2 sound.
14671
+ * @returns The rendered component.
14672
+ */
14673
+ const AudioV2SoundGeneralProperties = ({ sound, selectionService }) => {
14674
+ const state = useSoundState(sound);
14675
+ const volume = useObservableState(useCallback(() => sound.volume, [sound]), useInterceptObservable("function", sound, "setVolume"));
14676
+ return (jsxs(Fragment, { children: [jsx(TextPropertyLine, { label: "State", value: GetSoundStateLabel(state) }), jsx(Property, { component: SyncedSliderPropertyLine, label: "Volume", functionPath: "setVolume", value: volume, min: 0, max: 1, step: 0.01, onChange: (value) => sound.setVolume(value) }), jsx(AudioV2OutputBusLink, { target: sound, description: "The bus this sound routes its output to.", selectionService: selectionService })] }));
14677
+ };
14678
+ /**
14679
+ * Playback properties shared by all v2 sounds (loop, start offset, current time).
14680
+ * @returns The rendered component.
14681
+ */
14682
+ const AudioV2SoundPlaybackProperties = ({ sound }) => {
14683
+ // currentTime advances implicitly as the sound plays — poll to keep the display in sync.
14684
+ // Note: this is total elapsed playback time, not position-within-buffer — it keeps growing
14685
+ // past buffer.duration when looping. That's why it's rendered as a number input rather
14686
+ // than a scrub-style slider.
14687
+ const tickObservable = usePollingObservable(100);
14688
+ const currentTime = useObservableState(useCallback(() => sound.currentTime, [sound]), tickObservable, useInterceptObservable("property", sound, "currentTime"));
14689
+ return (jsxs(Fragment, { children: [jsx(BoundProperty, { component: SwitchPropertyLine, label: "Loop", target: sound, propertyKey: "loop" }), jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Start Offset", target: sound, propertyKey: "startOffset", min: 0, step: 0.1, unit: "s" }), jsx(Property, { component: NumberInputPropertyLine, label: "Current Time", propertyPath: "currentTime", value: currentTime, min: 0, step: 0.1, unit: "s", onChange: (value) => (sound.currentTime = value) })] }));
14690
+ };
14691
+ /**
14692
+ * Additional playback properties specific to {@link StaticSound} (duration, loop range, pitch, playback rate).
14693
+ * @returns The rendered component.
14694
+ */
14695
+ const AudioV2StaticSoundPlaybackProperties = ({ sound }) => {
14696
+ return (jsxs(Fragment, { children: [jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Duration", target: sound, propertyKey: "duration", min: 0, step: 0.1, unit: "s" }), jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Loop Start", target: sound, propertyKey: "loopStart", min: 0, step: 0.1, unit: "s" }), jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Loop End", target: sound, propertyKey: "loopEnd", min: 0, step: 0.1, unit: "s" }), jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Pitch", target: sound, propertyKey: "pitch", step: 1, unit: "\u00A2" }), jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Playback Rate", target: sound, propertyKey: "playbackRate", min: 0, step: 0.1 })] }));
14697
+ };
14698
+ /**
14699
+ * Preload status display for {@link StreamingSound}.
14700
+ * @returns The rendered component.
14701
+ */
14702
+ const AudioV2StreamingSoundPreloadProperties = ({ sound }) => {
14703
+ return (jsxs(Fragment, { children: [jsx(TextPropertyLine, { label: "Preload Count", value: String(sound.preloadCount) }), jsx(TextPropertyLine, { label: "Preload Completed", value: String(sound.preloadCompletedCount) })] }));
14704
+ };
14705
+ /**
14706
+ * Play / pause / stop controls for any v2 sound.
14707
+ * @returns The rendered component.
14708
+ */
14709
+ const AudioV2SoundCommandsProperties = ({ sound }) => {
14710
+ const state = useSoundState(sound);
14711
+ const isPlaying = state === 3 /* SoundState.Started */ || state === 2 /* SoundState.Starting */;
14712
+ const isPaused = state === 5 /* SoundState.Paused */;
14713
+ return (jsxs(Fragment, { children: [jsx(ButtonLine, { uniqueId: "audiov2-sound-play-pause", label: isPlaying ? "Pause" : "Play", icon: isPlaying ? PauseRegular : PlayRegular, onClick: () => {
14714
+ if (isPlaying) {
14715
+ sound.pause();
14716
+ }
14717
+ else if (isPaused) {
14718
+ sound.resume();
14719
+ }
14720
+ else {
14721
+ sound.play();
14722
+ }
14723
+ } }), jsx(ButtonLine, { uniqueId: "audiov2-sound-stop", label: "Stop", icon: StopRegular, onClick: () => sound.stop() })] }));
14724
+ };
14725
+ // -----------------------------------------------------------------------------
14726
+ // Standalone sound sources (e.g. microphone)
14727
+ // -----------------------------------------------------------------------------
14728
+ /**
14729
+ * General properties for a v2 sound source that is not a sound (e.g. microphone).
14730
+ * @returns The rendered component.
14731
+ */
14732
+ const AudioV2SoundSourceGeneralProperties = ({ source, selectionService }) => {
14733
+ const volume = useObservableState(useCallback(() => source.volume, [source]), useInterceptObservable("function", source, "setVolume"));
14734
+ return (jsxs(Fragment, { children: [jsx(Property, { component: SyncedSliderPropertyLine, label: "Volume", functionPath: "setVolume", value: volume, min: 0, max: 1, step: 0.01, onChange: (value) => source.setVolume(value) }), jsx(AudioV2OutputBusLink, { target: source, description: "The bus this source routes its output to.", selectionService: selectionService })] }));
14735
+ };
14736
+
14289
14737
  const AudioPropertiesServiceDefinition = {
14290
14738
  friendlyName: "Audio Properties",
14291
- consumes: [PropertiesServiceIdentity],
14292
- factory: (propertiesService) => {
14293
- const soundContentRegistration = propertiesService.addSectionContent({
14739
+ consumes: [PropertiesServiceIdentity, SelectionServiceIdentity],
14740
+ factory: (propertiesService, selectionService) => {
14741
+ // --- v1 Sound ---
14742
+ const soundV1ContentRegistration = propertiesService.addSectionContent({
14294
14743
  key: "Sound General Properties",
14295
14744
  predicate: (entity) => entity instanceof Sound,
14296
14745
  content: [
@@ -14304,9 +14753,121 @@ const AudioPropertiesServiceDefinition = {
14304
14753
  },
14305
14754
  ],
14306
14755
  });
14756
+ // --- v2 AudioEngineV2 ---
14757
+ const engineV2ContentRegistration = propertiesService.addSectionContent({
14758
+ key: "Audio V2 Engine Properties",
14759
+ predicate: (entity) => entity instanceof AudioEngineV2,
14760
+ content: [
14761
+ {
14762
+ section: "General",
14763
+ component: ({ context }) => jsx(AudioV2EngineGeneralProperties, { engine: context }),
14764
+ },
14765
+ {
14766
+ section: "Listener",
14767
+ component: ({ context }) => jsx(AudioV2EngineListenerProperties, { engine: context, selectionService: selectionService }),
14768
+ },
14769
+ {
14770
+ section: "Commands",
14771
+ component: ({ context }) => jsx(AudioV2EngineCommandsProperties, { engine: context }),
14772
+ },
14773
+ ],
14774
+ });
14775
+ // --- v2 Buses (any AbstractAudioBus — covers Main + Audio) ---
14776
+ const busV2ContentRegistration = propertiesService.addSectionContent({
14777
+ key: "Audio V2 Bus Properties",
14778
+ predicate: (entity) => entity instanceof AbstractAudioBus,
14779
+ content: [
14780
+ {
14781
+ section: "General",
14782
+ component: ({ context }) => jsx(AudioV2BusGeneralProperties, { bus: context }),
14783
+ },
14784
+ ],
14785
+ });
14786
+ // --- v2 AudioBus (non-main bus; adds an Output Bus link) ---
14787
+ const audioBusV2ContentRegistration = propertiesService.addSectionContent({
14788
+ key: "Audio V2 AudioBus Properties",
14789
+ predicate: (entity) => entity instanceof AudioBus,
14790
+ content: [
14791
+ {
14792
+ section: "General",
14793
+ component: ({ context }) => jsx(AudioV2AudioBusGeneralProperties, { bus: context, selectionService: selectionService }),
14794
+ },
14795
+ ],
14796
+ });
14797
+ // --- v2 Sounds (Static + Streaming) ---
14798
+ const soundV2ContentRegistration = propertiesService.addSectionContent({
14799
+ key: "Audio V2 Sound Properties",
14800
+ predicate: (entity) => entity instanceof AbstractSound,
14801
+ content: [
14802
+ {
14803
+ section: "General",
14804
+ component: ({ context }) => jsx(AudioV2SoundGeneralProperties, { sound: context, selectionService: selectionService }),
14805
+ },
14806
+ {
14807
+ section: "Playback",
14808
+ component: ({ context }) => jsx(AudioV2SoundPlaybackProperties, { sound: context }),
14809
+ },
14810
+ {
14811
+ section: "Commands",
14812
+ component: ({ context }) => jsx(AudioV2SoundCommandsProperties, { sound: context }),
14813
+ },
14814
+ ],
14815
+ });
14816
+ // --- v2 StaticSound (extra playback properties) ---
14817
+ const staticSoundV2ContentRegistration = propertiesService.addSectionContent({
14818
+ key: "Audio V2 Static Sound Properties",
14819
+ predicate: (entity) => entity instanceof StaticSound,
14820
+ content: [
14821
+ {
14822
+ section: "Playback",
14823
+ component: ({ context }) => jsx(AudioV2StaticSoundPlaybackProperties, { sound: context }),
14824
+ },
14825
+ ],
14826
+ });
14827
+ // --- v2 StreamingSound (preload status) ---
14828
+ const streamingSoundV2ContentRegistration = propertiesService.addSectionContent({
14829
+ key: "Audio V2 Streaming Sound Properties",
14830
+ predicate: (entity) => entity instanceof StreamingSound,
14831
+ content: [
14832
+ {
14833
+ section: "Streaming",
14834
+ component: ({ context }) => jsx(AudioV2StreamingSoundPreloadProperties, { sound: context }),
14835
+ },
14836
+ ],
14837
+ });
14838
+ // --- v2 Sound Sources (microphone, audio-node) — non-Sound only ---
14839
+ const soundSourceV2ContentRegistration = propertiesService.addSectionContent({
14840
+ key: "Audio V2 Sound Source Properties",
14841
+ predicate: (entity) => entity instanceof AbstractSoundSource && !(entity instanceof AbstractSound),
14842
+ content: [
14843
+ {
14844
+ section: "General",
14845
+ component: ({ context }) => jsx(AudioV2SoundSourceGeneralProperties, { source: context, selectionService: selectionService }),
14846
+ },
14847
+ ],
14848
+ });
14849
+ // --- v2 Spatial attachment (any AbstractSoundSource currently spatial) ---
14850
+ const spatialV2ContentRegistration = propertiesService.addSectionContent({
14851
+ key: "Audio V2 Spatial Properties",
14852
+ predicate: (entity) => entity instanceof AbstractSoundSource && entity._isSpatial,
14853
+ content: [
14854
+ {
14855
+ section: "Spatial",
14856
+ component: ({ context }) => jsx(AudioV2SpatialAttachmentProperties, { source: context, selectionService: selectionService }),
14857
+ },
14858
+ ],
14859
+ });
14307
14860
  return {
14308
14861
  dispose: () => {
14309
- soundContentRegistration.dispose();
14862
+ soundV1ContentRegistration.dispose();
14863
+ engineV2ContentRegistration.dispose();
14864
+ busV2ContentRegistration.dispose();
14865
+ audioBusV2ContentRegistration.dispose();
14866
+ soundV2ContentRegistration.dispose();
14867
+ staticSoundV2ContentRegistration.dispose();
14868
+ streamingSoundV2ContentRegistration.dispose();
14869
+ soundSourceV2ContentRegistration.dispose();
14870
+ spatialV2ContentRegistration.dispose();
14310
14871
  },
14311
14872
  };
14312
14873
  },
@@ -14740,7 +15301,7 @@ function useObservableArray(target, getItems, addFn, removeFn, changeFn) {
14740
15301
  }, [getItems]), useInterceptObservable("function", target, addFn), useInterceptObservable("function", target, removeFn), changeFn ? useInterceptObservable("function", target, changeFn) : undefined);
14741
15302
  }
14742
15303
 
14743
- const useStyles$p = makeStyles({
15304
+ const useStyles$q = makeStyles({
14744
15305
  lightsListDiv: {
14745
15306
  display: "flex",
14746
15307
  flexDirection: "column",
@@ -14750,7 +15311,7 @@ const ClusteredLightContainerSetupProperties = ({ context: container }) => {
14750
15311
  return (jsxs(Fragment, { children: [jsx(BooleanBadgePropertyLine, { label: "Is Supported", value: container.isSupported }), jsx(BoundProperty, { label: "Horizontal Tiles", component: NumberInputPropertyLine, target: container, propertyKey: "horizontalTiles", step: 1, min: 1, forceInt: true }), jsx(BoundProperty, { label: "Vertical Tiles", component: NumberInputPropertyLine, target: container, propertyKey: "verticalTiles", step: 1, min: 1, forceInt: true }), jsx(BoundProperty, { label: "Depth Slices", component: NumberInputPropertyLine, target: container, propertyKey: "depthSlices", step: 1, min: 1, forceInt: true }), jsx(BoundProperty, { label: "Max Range", component: NumberInputPropertyLine, target: container, propertyKey: "maxRange", min: 1 })] }));
14751
15312
  };
14752
15313
  const ClusteredLightContainerLightsProperties = ({ container, selectionService, }) => {
14753
- const classes = useStyles$p();
15314
+ const classes = useStyles$q();
14754
15315
  const lights = useObservableArray(container, useCallback(() => container.lights, [container]), "addLight", "removeLight");
14755
15316
  return (jsx(PropertyLine, { label: "Lights", expandedContent: jsx("div", { className: classes.lightsListDiv, children: lights.map((light) => (jsx(LinkToEntityPropertyLine, { label: light.getClassName(), entity: light, selectionService: selectionService }, light.uniqueId))) }), children: jsx(Badge, { appearance: "filled", children: lights.length }) }));
14756
15317
  };
@@ -14800,7 +15361,7 @@ const HemisphericLightSetupProperties = ({ context: hemisphericLight }) => {
14800
15361
  return (jsxs(Fragment, { children: [jsx(BoundProperty, { label: "Direction", component: Vector3PropertyLine, target: hemisphericLight, propertyKey: "direction" }), jsx(BoundProperty, { label: "Diffuse", component: Color3PropertyLine, target: hemisphericLight, propertyKey: "diffuse" }), jsx(BoundProperty, { label: "Ground", component: Color3PropertyLine, target: hemisphericLight, propertyKey: "groundColor" }), jsx(BoundProperty, { label: "Intensity", component: NumberInputPropertyLine, target: hemisphericLight, propertyKey: "intensity" })] }));
14801
15362
  };
14802
15363
 
14803
- const useStyles$o = makeStyles({
15364
+ const useStyles$p = makeStyles({
14804
15365
  root: {
14805
15366
  display: "grid",
14806
15367
  gridTemplateRows: "repeat(1fr)",
@@ -14830,7 +15391,7 @@ const useStyles$o = makeStyles({
14830
15391
  const ComboBox = forwardRef((props, ref) => {
14831
15392
  ComboBox.displayName = "ComboBox";
14832
15393
  const comboId = useId();
14833
- const styles = useStyles$o();
15394
+ const styles = useStyles$p();
14834
15395
  const { size } = useContext(ToolContext);
14835
15396
  // Find the label for the current value
14836
15397
  const getLabel = (value) => props.options.find((opt) => opt.value === value)?.label ?? "";
@@ -14853,7 +15414,7 @@ const ComboBox = forwardRef((props, ref) => {
14853
15414
  return (jsxs("div", { className: styles.root, children: [jsx("label", { id: comboId, children: props.label }), jsx(Combobox, { ref: ref, defaultOpen: props.defaultOpen, size: size, root: { className: styles.comboBox }, input: { className: styles.input }, listbox: { className: styles.listbox }, onOptionSelect: onOptionSelect, "aria-labelledby": comboId, placeholder: "Search..", onChange: (ev) => setQuery(ev.target.value), value: query, children: children })] }));
14854
15415
  });
14855
15416
 
14856
- const useStyles$n = makeStyles({
15417
+ const useStyles$o = makeStyles({
14857
15418
  linkDiv: {
14858
15419
  display: "flex",
14859
15420
  flexDirection: "row",
@@ -14878,7 +15439,7 @@ const useStyles$n = makeStyles({
14878
15439
  function EntitySelector(props) {
14879
15440
  const { value, onLink, getEntities, getName, filter, defaultValue } = props;
14880
15441
  const onChange = props.onChange;
14881
- const classes = useStyles$n();
15442
+ const classes = useStyles$o();
14882
15443
  const comboBoxRef = useRef(null);
14883
15444
  // Build options with uniqueId as key
14884
15445
  const options = useMemo(() => {
@@ -15032,7 +15593,7 @@ const TextureUpload = (props) => {
15032
15593
  return jsx(UploadButton, { onUpload: handleUpload, accept: accept, title: "Upload Texture", label: label });
15033
15594
  };
15034
15595
 
15035
- const useStyles$m = makeStyles({
15596
+ const useStyles$n = makeStyles({
15036
15597
  container: {
15037
15598
  display: "flex",
15038
15599
  flexDirection: "row",
@@ -15049,7 +15610,7 @@ const useStyles$m = makeStyles({
15049
15610
  const TextureSelector = (props) => {
15050
15611
  TextureSelector.displayName = "TextureSelector";
15051
15612
  const { scene, cubeOnly, value, onChange, onLink, defaultValue } = props;
15052
- const classes = useStyles$m();
15613
+ const classes = useStyles$n();
15053
15614
  const getTextures = useCallback(() => scene.textures, [scene.textures]);
15054
15615
  const getName = useCallback((texture) => texture.displayName || texture.name || `${texture.getClassName() || "Unnamed Texture"} (${texture.uniqueId})`, []);
15055
15616
  const filter = useCallback((texture) => !cubeOnly || texture.isCube, [cubeOnly]);
@@ -15654,7 +16215,7 @@ async function EditNodeMaterial(material) {
15654
16215
  await material.edit({ nodeEditorConfig: { backgroundColor: material.getScene().clearColor } });
15655
16216
  }
15656
16217
 
15657
- const useStyles$l = makeStyles({
16218
+ const useStyles$m = makeStyles({
15658
16219
  subsection: {
15659
16220
  marginTop: tokens.spacingVerticalM,
15660
16221
  },
@@ -15728,7 +16289,7 @@ const GradientBlockPropertyLine = (props) => {
15728
16289
  };
15729
16290
  const NodeMaterialInputProperties = (props) => {
15730
16291
  const { material } = props;
15731
- const classes = useStyles$l();
16292
+ const classes = useStyles$m();
15732
16293
  const inputBlocks = useObservableState(useCallback(() => {
15733
16294
  const inspectorVisibleInputBlocks = material
15734
16295
  .getInputBlocks()
@@ -15790,6 +16351,115 @@ const CheckboxPropertyLine = (props) => {
15790
16351
  return (jsx(PropertyLine, { ...props, children: jsx(Checkbox, { ...props }) }));
15791
16352
  };
15792
16353
 
16354
+ const useStyles$l = makeStyles({
16355
+ expandedDebugContainer: {
16356
+ display: "flex",
16357
+ alignItems: "center",
16358
+ marginTop: tokens.spacingVerticalXS,
16359
+ },
16360
+ debugLabel: {
16361
+ flex: 1,
16362
+ },
16363
+ });
16364
+ /**
16365
+ * Toggles debug mode for a texture on a material.
16366
+ * When enabled, creates a debug `StandardMaterial` with the texture wired as `emissiveTexture` and swaps it onto every
16367
+ * mesh that currently uses the original material. Enabling debug for one texture on a material disables it for any
16368
+ * other texture on that material (mutually exclusive).
16369
+ *
16370
+ * Notes:
16371
+ * - The texture is rendered using its current `level`. Textures whose level is not 1 will appear modulated; this is
16372
+ * intentional so that no global state on the source texture is mutated (a level mutation would affect every other
16373
+ * material that references the same texture instance).
16374
+ * - Only meshes referencing the original material at toggle time receive the debug material. Meshes added or
16375
+ * reassigned to the original material afterwards are not affected until the next toggle cycle.
16376
+ * @param material - The material to debug
16377
+ * @param texture - The texture to debug
16378
+ * @param enable - Whether to enable or disable debug mode
16379
+ */
16380
+ function ToggleTextureDebug(material, texture, enable) {
16381
+ if (!material || !texture) {
16382
+ return;
16383
+ }
16384
+ const scene = material.getScene();
16385
+ material.reservedDataStore ?? (material.reservedDataStore = {});
16386
+ const store = material.reservedDataStore;
16387
+ store.debugTexture ?? (store.debugTexture = null);
16388
+ store.debugMaterial ?? (store.debugMaterial = null);
16389
+ const isCurrentlyDebugging = store.debugTexture === texture;
16390
+ if (enable && !isCurrentlyDebugging) {
16391
+ // If we were debugging a different texture, clean it up first.
16392
+ if (store.debugTexture) {
16393
+ ToggleTextureDebug(material, store.debugTexture, false);
16394
+ }
16395
+ const debugMaterial = new StandardMaterial("debugMaterial", scene);
16396
+ debugMaterial.disableLighting = true;
16397
+ debugMaterial.sideOrientation = material.sideOrientation;
16398
+ debugMaterial.emissiveTexture = texture;
16399
+ debugMaterial.forceDepthWrite = true;
16400
+ debugMaterial.reservedDataStore = { hidden: true };
16401
+ for (const mesh of scene.meshes) {
16402
+ if (mesh.material === material) {
16403
+ mesh.material = debugMaterial;
16404
+ }
16405
+ }
16406
+ store.debugMaterial = debugMaterial;
16407
+ // Assign debugTexture LAST so observers see a fully-populated store.
16408
+ store.debugTexture = texture;
16409
+ }
16410
+ else if (!enable && isCurrentlyDebugging) {
16411
+ const debugMaterial = store.debugMaterial;
16412
+ if (debugMaterial) {
16413
+ for (const mesh of scene.meshes) {
16414
+ if (mesh.material === debugMaterial) {
16415
+ mesh.material = material;
16416
+ }
16417
+ }
16418
+ debugMaterial.dispose();
16419
+ store.debugMaterial = null;
16420
+ }
16421
+ store.debugTexture = null;
16422
+ }
16423
+ }
16424
+ /**
16425
+ * A texture selector property line with material texture debug capability.
16426
+ * Displays a debug toggle in expanded content to render the selected texture as emissive on the material.
16427
+ * Enabling debug on one texture for a given material disables it for any other texture on that material.
16428
+ * @param props - The texture selector property line props plus material reference
16429
+ * @returns The property line element with debug toggle
16430
+ */
16431
+ const MaterialTextureDebugPropertyLine = (props) => {
16432
+ const classes = useStyles$l();
16433
+ const { material, ...textureProps } = props;
16434
+ const texture = props.value;
16435
+ const reservedDataStore = useProperty(material, "reservedDataStore");
16436
+ const debugTexture = useProperty(reservedDataStore, "debugTexture");
16437
+ const isDebugEnabled = !!texture && debugTexture === texture;
16438
+ // Track the texture this slot last rendered with so we can detect when its value changes away
16439
+ // from the texture currently being debugged. Without this, reassigning the slot's texture (or
16440
+ // setting it to null) while debug is active would leave the scene stuck in debug view with no
16441
+ // visible toggle to turn it off (the toggle for that slot is keyed off texture-instance
16442
+ // equality and would just appear off).
16443
+ const prevTextureRef = useRef(texture);
16444
+ useEffect(() => {
16445
+ const prevTexture = prevTextureRef.current;
16446
+ prevTextureRef.current = texture;
16447
+ if (prevTexture && prevTexture !== texture) {
16448
+ const store = material.reservedDataStore;
16449
+ if (store?.debugTexture === prevTexture) {
16450
+ ToggleTextureDebug(material, prevTexture, false);
16451
+ }
16452
+ }
16453
+ }, [texture]);
16454
+ const handleDebugToggle = useCallback((checked) => {
16455
+ if (texture) {
16456
+ ToggleTextureDebug(material, texture, checked);
16457
+ }
16458
+ }, [material, texture]);
16459
+ const expandedContent = texture ? (jsxs("div", { className: classes.expandedDebugContainer, children: [jsx("span", { className: classes.debugLabel, children: "Display Texture for Debug" }), jsx(Switch, { value: isDebugEnabled, onChange: handleDebugToggle })] })) : undefined;
16460
+ return (jsx(PropertyLine, { ...textureProps, expandedContent: expandedContent, children: jsx(TextureSelector, { ...textureProps }) }));
16461
+ };
16462
+
15793
16463
  const LightFalloffOptions = [
15794
16464
  { label: "Physical", value: PBRBaseMaterial.LIGHTFALLOFF_PHYSICAL },
15795
16465
  { label: "glTF", value: PBRBaseMaterial.LIGHTFALLOFF_GLTF },
@@ -15894,7 +16564,7 @@ const PBRBaseMaterialChannelsProperties = (props) => {
15894
16564
  const { material, selectionService } = props;
15895
16565
  const scene = material.getScene();
15896
16566
  const selectEntity = (entity) => (selectionService.selectedEntity = entity);
15897
- return (jsxs(Fragment, { children: [jsx(BoundProperty, { component: TextureSelectorPropertyLine, label: "Albedo", target: material, propertyKey: "_albedoTexture", scene: scene, onLink: selectEntity, defaultValue: null }), jsx(BoundProperty, { component: TextureSelectorPropertyLine, label: "Base Weight", target: material, propertyKey: "_baseWeightTexture", scene: scene, onLink: selectEntity, defaultValue: null }), jsx(BoundProperty, { component: TextureSelectorPropertyLine, label: "Base Diffuse Roughness", target: material, propertyKey: "_baseDiffuseRoughnessTexture", scene: scene, onLink: selectEntity, defaultValue: null }), jsx(BoundProperty, { component: TextureSelectorPropertyLine, label: "Metallic Roughness", target: material, propertyKey: "_metallicTexture", scene: scene, onLink: selectEntity, defaultValue: null }), jsx(BoundProperty, { component: TextureSelectorPropertyLine, uniqueId: "PBRBaseMaterialChannels_Reflection", label: "Reflection", target: material, propertyKey: "_reflectionTexture", scene: scene, cubeOnly: true, onLink: selectEntity, defaultValue: null }), jsx(BoundProperty, { component: TextureSelectorPropertyLine, label: "Refraction", target: material.subSurface, propertyKey: "refractionTexture", propertyPath: "subSurface.refractionTexture", scene: scene, onLink: selectEntity, defaultValue: null }), jsx(BoundProperty, { component: TextureSelectorPropertyLine, label: "Reflectivity", target: material, propertyKey: "_reflectivityTexture", scene: scene, onLink: selectEntity, defaultValue: null }), jsx(BoundProperty, { component: TextureSelectorPropertyLine, label: "Micro Surface", target: material, propertyKey: "_microSurfaceTexture", scene: scene, onLink: selectEntity, defaultValue: null }), jsx(BoundProperty, { component: TextureSelectorPropertyLine, label: "Bump", target: material, propertyKey: "_bumpTexture", scene: scene, onLink: selectEntity, defaultValue: null }), jsx(BoundProperty, { component: TextureSelectorPropertyLine, label: "Emissive", target: material, propertyKey: "_emissiveTexture", scene: scene, onLink: selectEntity, defaultValue: null }), jsx(BoundProperty, { component: TextureSelectorPropertyLine, label: "Opacity", target: material, propertyKey: "_opacityTexture", scene: scene, onLink: selectEntity, defaultValue: null }), jsx(BoundProperty, { component: TextureSelectorPropertyLine, label: "Ambient", target: material, propertyKey: "_ambientTexture", scene: scene, onLink: selectEntity, defaultValue: null }), jsx(BoundProperty, { component: TextureSelectorPropertyLine, label: "Lightmap", target: material, propertyKey: "_lightmapTexture", scene: scene, onLink: selectEntity, defaultValue: null }), jsx(BoundProperty, { component: TextureSelectorPropertyLine, label: "Detailmap", target: material.detailMap, propertyKey: "texture", propertyPath: "detailMap.texture", scene: scene, onLink: selectEntity, defaultValue: null }), jsx(BoundProperty, { component: SwitchPropertyLine, label: "Use Lightmap as Shadowmap", target: material, propertyKey: "_useLightmapAsShadowmap" }), jsx(BoundProperty, { component: SwitchPropertyLine, label: "Use Detailmap", target: material.detailMap, propertyKey: "isEnabled", propertyPath: "detailMap.isEnabled" }), jsx(BoundProperty, { component: SwitchPropertyLine, label: "Use Decalmap", target: material.decalMap, propertyKey: "isEnabled", propertyPath: "decalMap.isEnabled" })] }));
16567
+ return (jsxs(Fragment, { children: [jsx(BoundProperty, { component: MaterialTextureDebugPropertyLine, label: "Albedo", target: material, propertyKey: "_albedoTexture", scene: scene, onLink: selectEntity, defaultValue: null, material: material }), jsx(BoundProperty, { component: MaterialTextureDebugPropertyLine, label: "Base Weight", target: material, propertyKey: "_baseWeightTexture", scene: scene, onLink: selectEntity, defaultValue: null, material: material }), jsx(BoundProperty, { component: MaterialTextureDebugPropertyLine, label: "Base Diffuse Roughness", target: material, propertyKey: "_baseDiffuseRoughnessTexture", scene: scene, onLink: selectEntity, defaultValue: null, material: material }), jsx(BoundProperty, { component: MaterialTextureDebugPropertyLine, label: "Metallic Roughness", target: material, propertyKey: "_metallicTexture", scene: scene, onLink: selectEntity, defaultValue: null, material: material }), jsx(BoundProperty, { component: MaterialTextureDebugPropertyLine, uniqueId: "PBRBaseMaterialChannels_Reflection", label: "Reflection", target: material, propertyKey: "_reflectionTexture", scene: scene, cubeOnly: true, onLink: selectEntity, defaultValue: null, material: material }), jsx(BoundProperty, { component: MaterialTextureDebugPropertyLine, label: "Refraction", target: material.subSurface, propertyKey: "refractionTexture", propertyPath: "subSurface.refractionTexture", scene: scene, onLink: selectEntity, defaultValue: null, material: material }), jsx(BoundProperty, { component: MaterialTextureDebugPropertyLine, label: "Reflectivity", target: material, propertyKey: "_reflectivityTexture", scene: scene, onLink: selectEntity, defaultValue: null, material: material }), jsx(BoundProperty, { component: MaterialTextureDebugPropertyLine, label: "Micro Surface", target: material, propertyKey: "_microSurfaceTexture", scene: scene, onLink: selectEntity, defaultValue: null, material: material }), jsx(BoundProperty, { component: MaterialTextureDebugPropertyLine, label: "Bump", target: material, propertyKey: "_bumpTexture", scene: scene, onLink: selectEntity, defaultValue: null, material: material }), jsx(BoundProperty, { component: MaterialTextureDebugPropertyLine, label: "Emissive", target: material, propertyKey: "_emissiveTexture", scene: scene, onLink: selectEntity, defaultValue: null, material: material }), jsx(BoundProperty, { component: MaterialTextureDebugPropertyLine, label: "Opacity", target: material, propertyKey: "_opacityTexture", scene: scene, onLink: selectEntity, defaultValue: null, material: material }), jsx(BoundProperty, { component: MaterialTextureDebugPropertyLine, label: "Ambient", target: material, propertyKey: "_ambientTexture", scene: scene, onLink: selectEntity, defaultValue: null, material: material }), jsx(BoundProperty, { component: MaterialTextureDebugPropertyLine, label: "Lightmap", target: material, propertyKey: "_lightmapTexture", scene: scene, onLink: selectEntity, defaultValue: null, material: material }), jsx(BoundProperty, { component: MaterialTextureDebugPropertyLine, label: "Detailmap", target: material.detailMap, propertyKey: "texture", propertyPath: "detailMap.texture", scene: scene, onLink: selectEntity, defaultValue: null, material: material }), jsx(BoundProperty, { component: SwitchPropertyLine, label: "Use Lightmap as Shadowmap", target: material, propertyKey: "_useLightmapAsShadowmap" }), jsx(BoundProperty, { component: SwitchPropertyLine, label: "Use Detailmap", target: material.detailMap, propertyKey: "isEnabled", propertyPath: "detailMap.isEnabled" }), jsx(BoundProperty, { component: SwitchPropertyLine, label: "Use Decalmap", target: material.decalMap, propertyKey: "isEnabled", propertyPath: "decalMap.isEnabled" })] }));
15898
16568
  };
15899
16569
  const PBRBaseMaterialLightingAndColorProperties = (props) => {
15900
16570
  const { material } = props;
@@ -15904,7 +16574,7 @@ const PBRBaseMaterialMetallicWorkflowProperties = (props) => {
15904
16574
  const { material, selectionService } = props;
15905
16575
  const scene = material.getScene();
15906
16576
  const selectEntity = (entity) => (selectionService.selectedEntity = entity);
15907
- return (jsxs(Fragment, { children: [jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Metallic", target: material, propertyKey: "_metallic", min: 0, max: 1, step: 0.01, nullable: true, defaultValue: 0 }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, uniqueId: "PBRBaseMaterialMetallicWorkflow_Roughness", label: "Roughness", target: material, propertyKey: "_roughness", min: 0, max: 1, step: 0.01, nullable: true, defaultValue: 0 }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Base Diffuse Roughness", target: material, propertyKey: "_baseDiffuseRoughness", min: 0, max: 1, step: 0.01, nullable: true, defaultValue: 0 }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Index of Refraction", target: material.subSurface, propertyKey: "indexOfRefraction", propertyPath: "subSurface.indexOfRefraction", min: 1, max: 3, step: 0.01 }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "F0 Factor", target: material, propertyKey: "_metallicF0Factor", min: 0, max: 1, step: 0.01 }), jsx(BoundProperty, { component: Color3PropertyLine, label: "Reflectance Color", target: material, propertyKey: "_metallicReflectanceColor", isLinearMode: true }), jsx(BoundProperty, { component: SwitchPropertyLine, label: "Metallic Only", description: "Use only metallic from MetallicReflectance texture", target: material, propertyKey: "_useOnlyMetallicFromMetallicReflectanceTexture" }), jsx(BoundProperty, { component: TextureSelectorPropertyLine, label: "Metallic Reflectance", target: material, propertyKey: "_metallicReflectanceTexture", scene: scene, onLink: selectEntity, defaultValue: null }), jsx(BoundProperty, { component: TextureSelectorPropertyLine, label: "Reflectance", target: material, propertyKey: "_reflectanceTexture", scene: scene, onLink: selectEntity, defaultValue: null })] }));
16577
+ return (jsxs(Fragment, { children: [jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Metallic", target: material, propertyKey: "_metallic", min: 0, max: 1, step: 0.01, nullable: true, defaultValue: 0 }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, uniqueId: "PBRBaseMaterialMetallicWorkflow_Roughness", label: "Roughness", target: material, propertyKey: "_roughness", min: 0, max: 1, step: 0.01, nullable: true, defaultValue: 0 }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Base Diffuse Roughness", target: material, propertyKey: "_baseDiffuseRoughness", min: 0, max: 1, step: 0.01, nullable: true, defaultValue: 0 }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Index of Refraction", target: material.subSurface, propertyKey: "indexOfRefraction", propertyPath: "subSurface.indexOfRefraction", min: 1, max: 3, step: 0.01 }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "F0 Factor", target: material, propertyKey: "_metallicF0Factor", min: 0, max: 1, step: 0.01 }), jsx(BoundProperty, { component: Color3PropertyLine, label: "Reflectance Color", target: material, propertyKey: "_metallicReflectanceColor", isLinearMode: true }), jsx(BoundProperty, { component: SwitchPropertyLine, label: "Metallic Only", description: "Use only metallic from MetallicReflectance texture", target: material, propertyKey: "_useOnlyMetallicFromMetallicReflectanceTexture" }), jsx(BoundProperty, { component: MaterialTextureDebugPropertyLine, label: "Metallic Reflectance", target: material, propertyKey: "_metallicReflectanceTexture", material: material, scene: scene, onLink: selectEntity, defaultValue: null }), jsx(BoundProperty, { component: MaterialTextureDebugPropertyLine, label: "Reflectance", target: material, propertyKey: "_reflectanceTexture", material: material, scene: scene, onLink: selectEntity, defaultValue: null })] }));
15908
16578
  };
15909
16579
  const PBRBaseMaterialClearCoatProperties = (props) => {
15910
16580
  const { material, selectionService } = props;
@@ -15971,7 +16641,7 @@ const PBRBaseMaterialDebugProperties = (props) => {
15971
16641
  */
15972
16642
  const OpenPBRMaterialBaseProperties = (props) => {
15973
16643
  const { material } = props;
15974
- return (jsxs(Fragment, { children: [jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Base Weight", target: material, propertyKey: "baseWeight", min: 0, max: 1, step: 0.01, description: "Controls how strong or visible the base aspect appears.", docLink: "https://academysoftwarefoundation.github.io/OpenPBR/index.html#model/basesubstrate" }), jsx(BoundProperty, { component: TextureSelectorPropertyLine, label: "Base Weight", target: material, propertyKey: "baseWeightTexture", scene: material.getScene(), defaultValue: null, onLink: (texture) => void 0 }), jsx(BoundProperty, { component: Color3PropertyLine, label: "Base Color", target: material, propertyKey: "baseColor", isLinearMode: true, description: "Sets the primary surface color of the material.", docLink: "https://academysoftwarefoundation.github.io/OpenPBR/index.html#model/basesubstrate" }), jsx(BoundProperty, { component: TextureSelectorPropertyLine, label: "Base Color", target: material, propertyKey: "baseColorTexture", scene: material.getScene(), defaultValue: null, onLink: (texture) => void 0 }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Base Metalness", target: material, propertyKey: "baseMetalness", min: 0, max: 1, step: 0.01, description: "Controls whether the material behaves as metal or non-metal. The parameter supersedes transmission_weight and subsurface_weight.", docLink: "https://academysoftwarefoundation.github.io/OpenPBR/index.html#model/basesubstrate" }), jsx(BoundProperty, { component: TextureSelectorPropertyLine, label: "Base Metalness", target: material, propertyKey: "baseMetalnessTexture", scene: material.getScene(), defaultValue: null, onLink: (texture) => void 0 }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Base Diffuse Roughness", target: material, propertyKey: "baseDiffuseRoughness", min: 0, max: 1, step: 0.01, description: "Softens the surface's base appearance. Higher values create matte or porous looks. Lower values are smoother.", docLink: "https://academysoftwarefoundation.github.io/OpenPBR/index.html#model/basesubstrate" }), jsx(BoundProperty, { component: TextureSelectorPropertyLine, label: "Base Diffuse Roughness", target: material, propertyKey: "baseDiffuseRoughnessTexture", scene: material.getScene(), defaultValue: null, onLink: (texture) => void 0 }), jsx(BoundProperty, { component: TextureSelectorPropertyLine, label: "Ambient Occlusion", target: material, propertyKey: "ambientOcclusionTexture", scene: material.getScene(), defaultValue: null, onLink: (texture) => void 0 })] }));
16644
+ return (jsxs(Fragment, { children: [jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Base Weight", target: material, propertyKey: "baseWeight", min: 0, max: 1, step: 0.01, description: "Controls how strong or visible the base aspect appears.", docLink: "https://academysoftwarefoundation.github.io/OpenPBR/index.html#model/basesubstrate" }), jsx(BoundProperty, { component: MaterialTextureDebugPropertyLine, label: "Base Weight", target: material, propertyKey: "baseWeightTexture", scene: material.getScene(), defaultValue: null, onLink: (texture) => void 0, material: material }), jsx(BoundProperty, { component: Color3PropertyLine, label: "Base Color", target: material, propertyKey: "baseColor", isLinearMode: true, description: "Sets the primary surface color of the material.", docLink: "https://academysoftwarefoundation.github.io/OpenPBR/index.html#model/basesubstrate" }), jsx(BoundProperty, { component: MaterialTextureDebugPropertyLine, label: "Base Color", target: material, propertyKey: "baseColorTexture", scene: material.getScene(), defaultValue: null, onLink: (texture) => void 0, material: material }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Base Metalness", target: material, propertyKey: "baseMetalness", min: 0, max: 1, step: 0.01, description: "Controls whether the material behaves as metal or non-metal. The parameter supersedes transmission_weight and subsurface_weight.", docLink: "https://academysoftwarefoundation.github.io/OpenPBR/index.html#model/basesubstrate" }), jsx(BoundProperty, { component: MaterialTextureDebugPropertyLine, label: "Base Metalness", target: material, propertyKey: "baseMetalnessTexture", scene: material.getScene(), defaultValue: null, onLink: (texture) => void 0, material: material }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Base Diffuse Roughness", target: material, propertyKey: "baseDiffuseRoughness", min: 0, max: 1, step: 0.01, description: "Softens the surface's base appearance. Higher values create matte or porous looks. Lower values are smoother.", docLink: "https://academysoftwarefoundation.github.io/OpenPBR/index.html#model/basesubstrate" }), jsx(BoundProperty, { component: MaterialTextureDebugPropertyLine, label: "Base Diffuse Roughness", target: material, propertyKey: "baseDiffuseRoughnessTexture", scene: material.getScene(), defaultValue: null, onLink: (texture) => void 0, material: material }), jsx(BoundProperty, { component: MaterialTextureDebugPropertyLine, label: "Ambient Occlusion", target: material, propertyKey: "ambientOcclusionTexture", scene: material.getScene(), defaultValue: null, onLink: (texture) => void 0, material: material })] }));
15975
16645
  };
15976
16646
  /**
15977
16647
  * Displays the specular layer properties of an OpenPBR material.
@@ -15980,15 +16650,15 @@ const OpenPBRMaterialBaseProperties = (props) => {
15980
16650
  */
15981
16651
  const OpenPBRMaterialSpecularProperties = (props) => {
15982
16652
  const { material } = props;
15983
- return (jsxs(Fragment, { children: [jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Specular Weight", target: material, propertyKey: "specularWeight", min: 0, max: 1, step: 0.01, description: "Controls how strong the reflections appear.", docLink: "https://academysoftwarefoundation.github.io/OpenPBR/index.html#model/basesubstrate" }), jsx(BoundProperty, { component: TextureSelectorPropertyLine, label: "Specular Weight", target: material, propertyKey: "specularWeightTexture", scene: material.getScene(), defaultValue: null, onLink: (texture) => void 0 }), jsx(BoundProperty, { component: Color3PropertyLine, label: "Specular Color", target: material, propertyKey: "specularColor", isLinearMode: true, description: "Tints the color of reflections.", docLink: "https://academysoftwarefoundation.github.io/OpenPBR/index.html#model/basesubstrate" }), jsx(BoundProperty, { component: TextureSelectorPropertyLine, label: "Specular Color", target: material, propertyKey: "specularColorTexture", scene: material.getScene(), defaultValue: null, onLink: (texture) => void 0 }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Specular Roughness", target: material, propertyKey: "specularRoughness", min: 0, max: 1, step: 0.01, description: "Controls how sharp or blurry reflections are.", docLink: "https://academysoftwarefoundation.github.io/OpenPBR/index.html#model/basesubstrate" }), jsx(BoundProperty, { component: TextureSelectorPropertyLine, label: "Specular Roughness", target: material, propertyKey: "specularRoughnessTexture", scene: material.getScene(), defaultValue: null, onLink: (texture) => void 0 }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Specular Roughness Anisotropy", target: material, propertyKey: "specularRoughnessAnisotropy", min: 0, max: 1, step: 0.01, description: "Stretches reflections in one direction for brushed or streaked looks. Requires specular_roughness > 0.", docLink: "https://academysoftwarefoundation.github.io/OpenPBR/index.html#model/microfacetmodel" }), jsx(BoundProperty, { component: TextureSelectorPropertyLine, label: "Specular Roughness Anisotropy", target: material, propertyKey: "specularRoughnessAnisotropyTexture", scene: material.getScene(), defaultValue: null, onLink: (texture) => void 0 }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Specular IOR", target: material, propertyKey: "specularIor", min: 1, max: 30, step: 0.01, description: "Index of refraction is a physical value controlling the reflective intensity and refraction. The parameter has no effect on metals.", docLink: "https://academysoftwarefoundation.github.io/OpenPBR/index.html#model/basesubstrate" })] }));
16653
+ return (jsxs(Fragment, { children: [jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Specular Weight", target: material, propertyKey: "specularWeight", min: 0, max: 1, step: 0.01, description: "Controls how strong the reflections appear.", docLink: "https://academysoftwarefoundation.github.io/OpenPBR/index.html#model/basesubstrate" }), jsx(BoundProperty, { component: MaterialTextureDebugPropertyLine, label: "Specular Weight", target: material, propertyKey: "specularWeightTexture", scene: material.getScene(), defaultValue: null, onLink: (texture) => void 0, material: material }), jsx(BoundProperty, { component: Color3PropertyLine, label: "Specular Color", target: material, propertyKey: "specularColor", isLinearMode: true, description: "Tints the color of reflections.", docLink: "https://academysoftwarefoundation.github.io/OpenPBR/index.html#model/basesubstrate" }), jsx(BoundProperty, { component: MaterialTextureDebugPropertyLine, label: "Specular Color", target: material, propertyKey: "specularColorTexture", scene: material.getScene(), defaultValue: null, onLink: (texture) => void 0, material: material }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Specular Roughness", target: material, propertyKey: "specularRoughness", min: 0, max: 1, step: 0.01, description: "Controls how sharp or blurry reflections are.", docLink: "https://academysoftwarefoundation.github.io/OpenPBR/index.html#model/basesubstrate" }), jsx(BoundProperty, { component: MaterialTextureDebugPropertyLine, label: "Specular Roughness", target: material, propertyKey: "specularRoughnessTexture", scene: material.getScene(), defaultValue: null, onLink: (texture) => void 0, material: material }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Specular Roughness Anisotropy", target: material, propertyKey: "specularRoughnessAnisotropy", min: 0, max: 1, step: 0.01, description: "Stretches reflections in one direction for brushed or streaked looks. Requires specular_roughness > 0.", docLink: "https://academysoftwarefoundation.github.io/OpenPBR/index.html#model/microfacetmodel" }), jsx(BoundProperty, { component: MaterialTextureDebugPropertyLine, label: "Specular Roughness Anisotropy", target: material, propertyKey: "specularRoughnessAnisotropyTexture", scene: material.getScene(), defaultValue: null, onLink: (texture) => void 0, material: material }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Specular IOR", target: material, propertyKey: "specularIor", min: 1, max: 30, step: 0.01, description: "Index of refraction is a physical value controlling the reflective intensity and refraction. The parameter has no effect on metals.", docLink: "https://academysoftwarefoundation.github.io/OpenPBR/index.html#model/basesubstrate" })] }));
15984
16654
  };
15985
16655
  const OpenPBRMaterialTransmissionProperties = (props) => {
15986
16656
  const { material } = props;
15987
- return (jsxs(Fragment, { children: [jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Transmission Weight", target: material, propertyKey: "transmissionWeight", min: 0, max: 1, step: 0.01, description: "Controls the presence of the transparency effect. The parameter is superseded by base_metalness.", docLink: "https://academysoftwarefoundation.github.io/OpenPBR/index.html#model/basesubstrate/translucentbase" }), jsx(BoundProperty, { component: TextureSelectorPropertyLine, label: "Transmission Weight", target: material, propertyKey: "transmissionWeightTexture", scene: material.getScene(), defaultValue: null, onLink: (texture) => void 0 }), jsx(BoundProperty, { component: Color3PropertyLine, label: "Transmission Color", target: material, propertyKey: "transmissionColor", isLinearMode: true, description: "Tints light passing through the material. Works with transmission_depth for realistic thickness-based coloring.", docLink: "https://academysoftwarefoundation.github.io/OpenPBR/index.html#model/basesubstrate/translucentbase" }), jsx(BoundProperty, { component: TextureSelectorPropertyLine, label: "Transmission Color", target: material, propertyKey: "transmissionColorTexture", scene: material.getScene(), defaultValue: null, onLink: (texture) => void 0 }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Transmission Depth (cm)", target: material, propertyKey: "transmissionDepth", min: 0, max: 50, step: 0.001, convertTo: (value) => value * 100, convertFrom: (value) => value / 100, description: "Controls how quickly light is absorbed with thickness. Distance is in scene units.", docLink: "https://academysoftwarefoundation.github.io/OpenPBR/index.html#model/basesubstrate/translucentbase" }), jsx(BoundProperty, { component: TextureSelectorPropertyLine, label: "Transmission Depth", target: material, propertyKey: "transmissionDepthTexture", scene: material.getScene(), defaultValue: null, onLink: (texture) => void 0 }), jsx(BoundProperty, { component: Color3PropertyLine, label: "Transmission Scatter", target: material, propertyKey: "transmissionScatter", isLinearMode: true, description: "Adds internal cloudiness to create materials like juice, honey, etc. Requires transmission_depth > 0.", docLink: "https://academysoftwarefoundation.github.io/OpenPBR/index.html#model/basesubstrate/translucentbase" }), jsx(BoundProperty, { component: TextureSelectorPropertyLine, label: "Transmission Scatter", target: material, propertyKey: "transmissionScatterTexture", scene: material.getScene(), defaultValue: null, onLink: (texture) => void 0 }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Transmission Scatter Anisotropy", target: material, propertyKey: "transmissionScatterAnisotropy", min: -1, max: 1, step: 0.01, description: "Shifts scattering forward/backward for clearer or hazier appearance depending on viewing angle.", docLink: "https://academysoftwarefoundation.github.io/OpenPBR/index.html#model/basesubstrate/translucentbase" }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Transmission Dispersion Abbe Number", target: material, propertyKey: "transmissionDispersionAbbeNumber", min: 1, max: 100, step: 1, description: "Physical value for the rainbow color separation in refraction.", docLink: "https://academysoftwarefoundation.github.io/OpenPBR/index.html#model/basesubstrate/translucentbase" }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Transmission Dispersion Scale", target: material, propertyKey: "transmissionDispersionScale", min: 0, max: 5, step: 0.01, description: "Strength of rainbow color separation in refraction.", docLink: "https://academysoftwarefoundation.github.io/OpenPBR/index.html#model/basesubstrate/translucentbase" }), jsx(BoundProperty, { component: TextureSelectorPropertyLine, label: "Transmission Dispersion Scale", target: material, propertyKey: "transmissionDispersionScaleTexture", scene: material.getScene(), defaultValue: null, onLink: (texture) => void 0 })] }));
16657
+ return (jsxs(Fragment, { children: [jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Transmission Weight", target: material, propertyKey: "transmissionWeight", min: 0, max: 1, step: 0.01, description: "Controls the presence of the transparency effect. The parameter is superseded by base_metalness.", docLink: "https://academysoftwarefoundation.github.io/OpenPBR/index.html#model/basesubstrate/translucentbase" }), jsx(BoundProperty, { component: MaterialTextureDebugPropertyLine, label: "Transmission Weight", target: material, propertyKey: "transmissionWeightTexture", scene: material.getScene(), defaultValue: null, onLink: (texture) => void 0, material: material }), jsx(BoundProperty, { component: Color3PropertyLine, label: "Transmission Color", target: material, propertyKey: "transmissionColor", isLinearMode: true, description: "Tints light passing through the material. Works with transmission_depth for realistic thickness-based coloring.", docLink: "https://academysoftwarefoundation.github.io/OpenPBR/index.html#model/basesubstrate/translucentbase" }), jsx(BoundProperty, { component: MaterialTextureDebugPropertyLine, label: "Transmission Color", target: material, propertyKey: "transmissionColorTexture", scene: material.getScene(), defaultValue: null, onLink: (texture) => void 0, material: material }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Transmission Depth (cm)", target: material, propertyKey: "transmissionDepth", min: 0, max: 50, step: 0.001, convertTo: (value) => value * 100, convertFrom: (value) => value / 100, description: "Controls how quickly light is absorbed with thickness. Distance is in scene units.", docLink: "https://academysoftwarefoundation.github.io/OpenPBR/index.html#model/basesubstrate/translucentbase" }), jsx(BoundProperty, { component: MaterialTextureDebugPropertyLine, label: "Transmission Depth", target: material, propertyKey: "transmissionDepthTexture", scene: material.getScene(), defaultValue: null, onLink: (texture) => void 0, material: material }), jsx(BoundProperty, { component: Color3PropertyLine, label: "Transmission Scatter", target: material, propertyKey: "transmissionScatter", isLinearMode: true, description: "Adds internal cloudiness to create materials like juice, honey, etc. Requires transmission_depth > 0.", docLink: "https://academysoftwarefoundation.github.io/OpenPBR/index.html#model/basesubstrate/translucentbase" }), jsx(BoundProperty, { component: MaterialTextureDebugPropertyLine, label: "Transmission Scatter", target: material, propertyKey: "transmissionScatterTexture", scene: material.getScene(), defaultValue: null, onLink: (texture) => void 0, material: material }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Transmission Scatter Anisotropy", target: material, propertyKey: "transmissionScatterAnisotropy", min: -1, max: 1, step: 0.01, description: "Shifts scattering forward/backward for clearer or hazier appearance depending on viewing angle.", docLink: "https://academysoftwarefoundation.github.io/OpenPBR/index.html#model/basesubstrate/translucentbase" }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Transmission Dispersion Abbe Number", target: material, propertyKey: "transmissionDispersionAbbeNumber", min: 1, max: 100, step: 1, description: "Physical value for the rainbow color separation in refraction.", docLink: "https://academysoftwarefoundation.github.io/OpenPBR/index.html#model/basesubstrate/translucentbase" }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Transmission Dispersion Scale", target: material, propertyKey: "transmissionDispersionScale", min: 0, max: 5, step: 0.01, description: "Strength of rainbow color separation in refraction.", docLink: "https://academysoftwarefoundation.github.io/OpenPBR/index.html#model/basesubstrate/translucentbase" }), jsx(BoundProperty, { component: MaterialTextureDebugPropertyLine, label: "Transmission Dispersion Scale", target: material, propertyKey: "transmissionDispersionScaleTexture", scene: material.getScene(), defaultValue: null, onLink: (texture) => void 0, material: material })] }));
15988
16658
  };
15989
16659
  const OpenPBRMaterialSubsurfaceProperties = (props) => {
15990
16660
  const { material } = props;
15991
- return (jsxs(Fragment, { children: [jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Subsurface Weight", target: material, propertyKey: "subsurfaceWeight", min: 0, max: 1, step: 0.01, description: "Controls the presence of the subsurface effect. The parameter is superseded by base_metalness and transmission_weight.", docLink: "https://academysoftwarefoundation.github.io/OpenPBR/index.html#model/basesubstrate/subsurface" }), jsx(BoundProperty, { component: TextureSelectorPropertyLine, label: "Subsurface Weight", target: material, propertyKey: "subsurfaceWeightTexture", scene: material.getScene(), defaultValue: null, onLink: (texture) => void 0 }), jsx(BoundProperty, { component: Color3PropertyLine, label: "Subsurface Color", target: material, propertyKey: "subsurfaceColor", isLinearMode: true, description: "Colors the light that scatters under the surface.", docLink: "https://academysoftwarefoundation.github.io/OpenPBR/index.html#model/basesubstrate/subsurface" }), jsx(BoundProperty, { component: TextureSelectorPropertyLine, label: "Subsurface Color", target: material, propertyKey: "subsurfaceColorTexture", scene: material.getScene(), defaultValue: null, onLink: (texture) => void 0 }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Subsurface Radius (cm)", target: material, propertyKey: "subsurfaceRadius", min: 0, max: 50, step: 0.001, convertTo: (value) => value * 100, convertFrom: (value) => value / 100, description: "Controls how soft and spread-out the subsurface look appears.", docLink: "https://academysoftwarefoundation.github.io/OpenPBR/index.html#model/basesubstrate/subsurface" }), jsx(BoundProperty, { component: Color3PropertyLine, label: "Subsurface Radius Scale", target: material, propertyKey: "subsurfaceRadiusScale", isLinearMode: true, description: "Tints thin areas with light shining through, like warm glow on ears or leaves.", docLink: "https://academysoftwarefoundation.github.io/OpenPBR/index.html#model/basesubstrate/subsurface" }), jsx(BoundProperty, { component: TextureSelectorPropertyLine, label: "Subsurface Radius Scale", target: material, propertyKey: "subsurfaceRadiusScaleTexture", scene: material.getScene(), defaultValue: null, onLink: (texture) => void 0 }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Subsurface Scatter Anisotropy", target: material, propertyKey: "subsurfaceScatterAnisotropy", min: -1, max: 1, step: 0.01, description: "Shifts scattering forward/backward for a softer glow or a sharper one.", docLink: "https://academysoftwarefoundation.github.io/OpenPBR/index.html#model/basesubstrate/subsurface" })] }));
16661
+ return (jsxs(Fragment, { children: [jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Subsurface Weight", target: material, propertyKey: "subsurfaceWeight", min: 0, max: 1, step: 0.01, description: "Controls the presence of the subsurface effect. The parameter is superseded by base_metalness and transmission_weight.", docLink: "https://academysoftwarefoundation.github.io/OpenPBR/index.html#model/basesubstrate/subsurface" }), jsx(BoundProperty, { component: MaterialTextureDebugPropertyLine, label: "Subsurface Weight", target: material, propertyKey: "subsurfaceWeightTexture", scene: material.getScene(), defaultValue: null, onLink: (texture) => void 0, material: material }), jsx(BoundProperty, { component: Color3PropertyLine, label: "Subsurface Color", target: material, propertyKey: "subsurfaceColor", isLinearMode: true, description: "Colors the light that scatters under the surface.", docLink: "https://academysoftwarefoundation.github.io/OpenPBR/index.html#model/basesubstrate/subsurface" }), jsx(BoundProperty, { component: MaterialTextureDebugPropertyLine, label: "Subsurface Color", target: material, propertyKey: "subsurfaceColorTexture", scene: material.getScene(), defaultValue: null, onLink: (texture) => void 0, material: material }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Subsurface Radius (cm)", target: material, propertyKey: "subsurfaceRadius", min: 0, max: 50, step: 0.001, convertTo: (value) => value * 100, convertFrom: (value) => value / 100, description: "Controls how soft and spread-out the subsurface look appears.", docLink: "https://academysoftwarefoundation.github.io/OpenPBR/index.html#model/basesubstrate/subsurface" }), jsx(BoundProperty, { component: Color3PropertyLine, label: "Subsurface Radius Scale", target: material, propertyKey: "subsurfaceRadiusScale", isLinearMode: true, description: "Tints thin areas with light shining through, like warm glow on ears or leaves.", docLink: "https://academysoftwarefoundation.github.io/OpenPBR/index.html#model/basesubstrate/subsurface" }), jsx(BoundProperty, { component: MaterialTextureDebugPropertyLine, label: "Subsurface Radius Scale", target: material, propertyKey: "subsurfaceRadiusScaleTexture", scene: material.getScene(), defaultValue: null, onLink: (texture) => void 0, material: material }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Subsurface Scatter Anisotropy", target: material, propertyKey: "subsurfaceScatterAnisotropy", min: -1, max: 1, step: 0.01, description: "Shifts scattering forward/backward for a softer glow or a sharper one.", docLink: "https://academysoftwarefoundation.github.io/OpenPBR/index.html#model/basesubstrate/subsurface" })] }));
15992
16662
  };
15993
16663
  /**
15994
16664
  * Displays the coat layer properties of an OpenPBR material.
@@ -15997,7 +16667,7 @@ const OpenPBRMaterialSubsurfaceProperties = (props) => {
15997
16667
  */
15998
16668
  const OpenPBRMaterialCoatProperties = (props) => {
15999
16669
  const { material } = props;
16000
- return (jsxs(Fragment, { children: [jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Coat Weight", target: material, propertyKey: "coatWeight", min: 0, max: 1, step: 0.01, description: "Controls the presence of the coat.", docLink: "https://academysoftwarefoundation.github.io/OpenPBR/index.html#model/coat" }), jsx(BoundProperty, { component: TextureSelectorPropertyLine, label: "Coat Weight", target: material, propertyKey: "coatWeightTexture", scene: material.getScene(), defaultValue: null, onLink: (texture) => void 0 }), jsx(BoundProperty, { component: Color3PropertyLine, label: "Coat Color", target: material, propertyKey: "coatColor", isLinearMode: true, description: "Tints the coat, for tinted varnish or paint.", docLink: "https://academysoftwarefoundation.github.io/OpenPBR/index.html#model/coat" }), jsx(BoundProperty, { component: TextureSelectorPropertyLine, label: "Coat Color", target: material, propertyKey: "coatColorTexture", scene: material.getScene(), defaultValue: null, onLink: (texture) => void 0 }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Coat Roughness", target: material, propertyKey: "coatRoughness", min: 0, max: 1, step: 0.01, description: "Controls how sharp or blurry the coat reflections appear.", docLink: "https://academysoftwarefoundation.github.io/OpenPBR/index.html#model/coat/roughening" }), jsx(BoundProperty, { component: TextureSelectorPropertyLine, label: "Coat Roughness", target: material, propertyKey: "coatRoughnessTexture", scene: material.getScene(), defaultValue: null, onLink: (texture) => void 0 }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Coat Roughness Anisotropy", target: material, propertyKey: "coatRoughnessAnisotropy", min: 0, max: 1, step: 0.01, description: "Stretches coat reflections in one direction for brushed or streaked looks. Requires coat_roughness > 0.", docLink: "https://academysoftwarefoundation.github.io/OpenPBR/index.html#model/coat" }), jsx(BoundProperty, { component: TextureSelectorPropertyLine, label: "Coat Roughness Anisotropy", target: material, propertyKey: "coatRoughnessAnisotropyTexture", scene: material.getScene(), defaultValue: null, onLink: (texture) => void 0 }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Coat IOR", target: material, propertyKey: "coatIor", min: 1, max: 3, step: 0.01, description: "Index of refraction is a physical value controlling the reflective intensity of the coat.", docLink: "https://academysoftwarefoundation.github.io/OpenPBR/index.html#model/coat" }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Coat Darkening", target: material, propertyKey: "coatDarkening", min: 0, max: 1, step: 0.01, description: "Darkens the base under the coat, similar to how real varnish deepens color.", docLink: "https://academysoftwarefoundation.github.io/OpenPBR/index.html#model/coat/darkening" }), jsx(BoundProperty, { component: TextureSelectorPropertyLine, label: "Coat Darkening", target: material, propertyKey: "coatDarkeningTexture", scene: material.getScene(), defaultValue: null, onLink: (texture) => void 0 })] }));
16670
+ return (jsxs(Fragment, { children: [jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Coat Weight", target: material, propertyKey: "coatWeight", min: 0, max: 1, step: 0.01, description: "Controls the presence of the coat.", docLink: "https://academysoftwarefoundation.github.io/OpenPBR/index.html#model/coat" }), jsx(BoundProperty, { component: MaterialTextureDebugPropertyLine, label: "Coat Weight", target: material, propertyKey: "coatWeightTexture", scene: material.getScene(), defaultValue: null, onLink: (texture) => void 0, material: material }), jsx(BoundProperty, { component: Color3PropertyLine, label: "Coat Color", target: material, propertyKey: "coatColor", isLinearMode: true, description: "Tints the coat, for tinted varnish or paint.", docLink: "https://academysoftwarefoundation.github.io/OpenPBR/index.html#model/coat" }), jsx(BoundProperty, { component: MaterialTextureDebugPropertyLine, label: "Coat Color", target: material, propertyKey: "coatColorTexture", scene: material.getScene(), defaultValue: null, onLink: (texture) => void 0, material: material }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Coat Roughness", target: material, propertyKey: "coatRoughness", min: 0, max: 1, step: 0.01, description: "Controls how sharp or blurry the coat reflections appear.", docLink: "https://academysoftwarefoundation.github.io/OpenPBR/index.html#model/coat/roughening" }), jsx(BoundProperty, { component: MaterialTextureDebugPropertyLine, label: "Coat Roughness", target: material, propertyKey: "coatRoughnessTexture", scene: material.getScene(), defaultValue: null, onLink: (texture) => void 0, material: material }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Coat Roughness Anisotropy", target: material, propertyKey: "coatRoughnessAnisotropy", min: 0, max: 1, step: 0.01, description: "Stretches coat reflections in one direction for brushed or streaked looks. Requires coat_roughness > 0.", docLink: "https://academysoftwarefoundation.github.io/OpenPBR/index.html#model/coat" }), jsx(BoundProperty, { component: MaterialTextureDebugPropertyLine, label: "Coat Roughness Anisotropy", target: material, propertyKey: "coatRoughnessAnisotropyTexture", scene: material.getScene(), defaultValue: null, onLink: (texture) => void 0, material: material }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Coat IOR", target: material, propertyKey: "coatIor", min: 1, max: 3, step: 0.01, description: "Index of refraction is a physical value controlling the reflective intensity of the coat.", docLink: "https://academysoftwarefoundation.github.io/OpenPBR/index.html#model/coat" }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Coat Darkening", target: material, propertyKey: "coatDarkening", min: 0, max: 1, step: 0.01, description: "Darkens the base under the coat, similar to how real varnish deepens color.", docLink: "https://academysoftwarefoundation.github.io/OpenPBR/index.html#model/coat/darkening" }), jsx(BoundProperty, { component: MaterialTextureDebugPropertyLine, label: "Coat Darkening", target: material, propertyKey: "coatDarkeningTexture", scene: material.getScene(), defaultValue: null, onLink: (texture) => void 0, material: material })] }));
16001
16671
  };
16002
16672
  /**
16003
16673
  * Displays the fuzz layer properties of an OpenPBR material.
@@ -16006,7 +16676,7 @@ const OpenPBRMaterialCoatProperties = (props) => {
16006
16676
  */
16007
16677
  const OpenPBRMaterialFuzzProperties = (props) => {
16008
16678
  const { material } = props;
16009
- return (jsxs(Fragment, { children: [jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Fuzz Weight", target: material, propertyKey: "fuzzWeight", min: 0, max: 1, step: 0.01, description: "Controls the presence of the fuzz.", docLink: "https://academysoftwarefoundation.github.io/OpenPBR/index.html#model/fuzz" }), jsx(BoundProperty, { component: TextureSelectorPropertyLine, label: "Fuzz Weight", target: material, propertyKey: "fuzzWeightTexture", scene: material.getScene(), defaultValue: null, onLink: (texture) => void 0 }), jsx(BoundProperty, { component: Color3PropertyLine, label: "Fuzz Color", target: material, propertyKey: "fuzzColor", isLinearMode: true, description: "Controls the color of the fuzz.", docLink: "https://academysoftwarefoundation.github.io/OpenPBR/index.html#model/fuzz" }), jsx(BoundProperty, { component: TextureSelectorPropertyLine, label: "Fuzz Color", target: material, propertyKey: "fuzzColorTexture", scene: material.getScene(), defaultValue: null, onLink: (texture) => void 0 }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Fuzz Roughness", target: material, propertyKey: "fuzzRoughness", min: 0, max: 1, step: 0.01, description: "Controls the roughness of the fuzz.", docLink: "https://academysoftwarefoundation.github.io/OpenPBR/index.html#model/fuzz" }), jsx(BoundProperty, { component: TextureSelectorPropertyLine, label: "Fuzz Roughness", target: material, propertyKey: "fuzzRoughnessTexture", scene: material.getScene(), defaultValue: null, onLink: (texture) => void 0 })] }));
16679
+ return (jsxs(Fragment, { children: [jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Fuzz Weight", target: material, propertyKey: "fuzzWeight", min: 0, max: 1, step: 0.01, description: "Controls the presence of the fuzz.", docLink: "https://academysoftwarefoundation.github.io/OpenPBR/index.html#model/fuzz" }), jsx(BoundProperty, { component: MaterialTextureDebugPropertyLine, label: "Fuzz Weight", target: material, propertyKey: "fuzzWeightTexture", scene: material.getScene(), defaultValue: null, onLink: (texture) => void 0, material: material }), jsx(BoundProperty, { component: Color3PropertyLine, label: "Fuzz Color", target: material, propertyKey: "fuzzColor", isLinearMode: true, description: "Controls the color of the fuzz.", docLink: "https://academysoftwarefoundation.github.io/OpenPBR/index.html#model/fuzz" }), jsx(BoundProperty, { component: MaterialTextureDebugPropertyLine, label: "Fuzz Color", target: material, propertyKey: "fuzzColorTexture", scene: material.getScene(), defaultValue: null, onLink: (texture) => void 0, material: material }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Fuzz Roughness", target: material, propertyKey: "fuzzRoughness", min: 0, max: 1, step: 0.01, description: "Controls the roughness of the fuzz.", docLink: "https://academysoftwarefoundation.github.io/OpenPBR/index.html#model/fuzz" }), jsx(BoundProperty, { component: MaterialTextureDebugPropertyLine, label: "Fuzz Roughness", target: material, propertyKey: "fuzzRoughnessTexture", scene: material.getScene(), defaultValue: null, onLink: (texture) => void 0, material: material })] }));
16010
16680
  };
16011
16681
  /**
16012
16682
  * Displays the emission properties of an OpenPBR material.
@@ -16015,7 +16685,7 @@ const OpenPBRMaterialFuzzProperties = (props) => {
16015
16685
  */
16016
16686
  const OpenPBRMaterialEmissionProperties = (props) => {
16017
16687
  const { material } = props;
16018
- return (jsxs(Fragment, { children: [jsx(BoundProperty, { component: Color3PropertyLine, label: "Emission Color", target: material, propertyKey: "emissionColor", isLinearMode: true, description: "Controls the color of the glow.", docLink: "https://academysoftwarefoundation.github.io/OpenPBR/index.html#model/emission" }), jsx(BoundProperty, { component: TextureSelectorPropertyLine, label: "Emission Color", target: material, propertyKey: "emissionColorTexture", scene: material.getScene(), defaultValue: null, onLink: (texture) => void 0 }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Emission Luminance", target: material, propertyKey: "emissionLuminance", min: 0, max: 10, step: 0.01, description: "Controls how bright the glow is.", docLink: "https://academysoftwarefoundation.github.io/OpenPBR/index.html#model/emission" })] }));
16688
+ return (jsxs(Fragment, { children: [jsx(BoundProperty, { component: Color3PropertyLine, label: "Emission Color", target: material, propertyKey: "emissionColor", isLinearMode: true, description: "Controls the color of the glow.", docLink: "https://academysoftwarefoundation.github.io/OpenPBR/index.html#model/emission" }), jsx(BoundProperty, { component: MaterialTextureDebugPropertyLine, label: "Emission Color", target: material, propertyKey: "emissionColorTexture", scene: material.getScene(), defaultValue: null, onLink: (texture) => void 0, material: material }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Emission Luminance", target: material, propertyKey: "emissionLuminance", min: 0, max: 10, step: 0.01, description: "Controls how bright the glow is.", docLink: "https://academysoftwarefoundation.github.io/OpenPBR/index.html#model/emission" })] }));
16019
16689
  };
16020
16690
  /**
16021
16691
  * Displays the thin film properties of an OpenPBR material.
@@ -16024,7 +16694,7 @@ const OpenPBRMaterialEmissionProperties = (props) => {
16024
16694
  */
16025
16695
  const OpenPBRMaterialThinFilmProperties = (props) => {
16026
16696
  const { material } = props;
16027
- return (jsxs(Fragment, { children: [jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Thin Film Weight", target: material, propertyKey: "thinFilmWeight", min: 0, max: 1, step: 0.01, description: "Controls the presence of the thin-film.", docLink: "https://academysoftwarefoundation.github.io/OpenPBR/index.html#model/thin-filmiridescence" }), jsx(BoundProperty, { component: TextureSelectorPropertyLine, label: "Thin Film Weight", target: material, propertyKey: "thinFilmWeightTexture", scene: material.getScene(), defaultValue: null, onLink: (texture) => void 0 }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Thin Film Thickness", target: material, propertyKey: "thinFilmThickness", min: 0, max: 1, step: 0.01, description: "Changes the color pattern of the iridescence.", docLink: "https://academysoftwarefoundation.github.io/OpenPBR/index.html#model/thin-filmiridescence" }), jsx(BoundProperty, { component: TextureSelectorPropertyLine, label: "Thin Film Thickness", target: material, propertyKey: "thinFilmThicknessTexture", scene: material.getScene(), defaultValue: null, onLink: (texture) => void 0 }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Thin Film IOR", target: material, propertyKey: "thinFilmIor", min: 1, max: 3, step: 0.01, description: "Alters the strength and contrast of the color shift based in the index of refraction.", docLink: "https://academysoftwarefoundation.github.io/OpenPBR/index.html#model/thin-filmiridescence" })] }));
16697
+ return (jsxs(Fragment, { children: [jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Thin Film Weight", target: material, propertyKey: "thinFilmWeight", min: 0, max: 1, step: 0.01, description: "Controls the presence of the thin-film.", docLink: "https://academysoftwarefoundation.github.io/OpenPBR/index.html#model/thin-filmiridescence" }), jsx(BoundProperty, { component: MaterialTextureDebugPropertyLine, label: "Thin Film Weight", target: material, propertyKey: "thinFilmWeightTexture", scene: material.getScene(), defaultValue: null, onLink: (texture) => void 0, material: material }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Thin Film Thickness", target: material, propertyKey: "thinFilmThickness", min: 0, max: 1, step: 0.01, description: "Changes the color pattern of the iridescence.", docLink: "https://academysoftwarefoundation.github.io/OpenPBR/index.html#model/thin-filmiridescence" }), jsx(BoundProperty, { component: MaterialTextureDebugPropertyLine, label: "Thin Film Thickness", target: material, propertyKey: "thinFilmThicknessTexture", scene: material.getScene(), defaultValue: null, onLink: (texture) => void 0, material: material }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Thin Film IOR", target: material, propertyKey: "thinFilmIor", min: 1, max: 3, step: 0.01, description: "Alters the strength and contrast of the color shift based in the index of refraction.", docLink: "https://academysoftwarefoundation.github.io/OpenPBR/index.html#model/thin-filmiridescence" })] }));
16028
16698
  };
16029
16699
  /**
16030
16700
  * Displays the geometry properties of an OpenPBR material.
@@ -16033,7 +16703,7 @@ const OpenPBRMaterialThinFilmProperties = (props) => {
16033
16703
  */
16034
16704
  const OpenPBRMaterialGeometryProperties = (props) => {
16035
16705
  const { material } = props;
16036
- return (jsxs(Fragment, { children: [jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Geometry Opacity", target: material, propertyKey: "geometryOpacity", min: 0, max: 1, step: 0.01, description: "Controls material presence and transparency cutout.", docLink: "https://academysoftwarefoundation.github.io/OpenPBR/index.html#model/opacity/transparency" }), jsx(BoundProperty, { component: TextureSelectorPropertyLine, label: "Geometry Opacity", target: material, propertyKey: "geometryOpacityTexture", scene: material.getScene(), defaultValue: null, onLink: (texture) => void 0 }), jsx(BoundProperty, { component: CheckboxPropertyLine, label: "Thin-Walled", target: material, propertyKey: "geometryThinWalled", description: "When enabled, treats material as a thin shell (like leaves, paper sheets or windows). Disables ray bending in refraction.", docLink: "https://academysoftwarefoundation.github.io/OpenPBR/index.html#model/thin-walledcase" }), jsx(BoundProperty, { component: TextureSelectorPropertyLine, label: "Geometry Normal", target: material, propertyKey: "geometryNormalTexture", scene: material.getScene(), defaultValue: null, onLink: (texture) => void 0 }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Tangent Angle", target: material, propertyKey: "geometryTangentAngle", min: 0, max: Math.PI, step: 0.01, description: "Tangent vector controlling anisotropic reflection direction for the base (metal and non-metal). Works with specular_roughness_anisotropy.", docLink: "https://academysoftwarefoundation.github.io/OpenPBR/index.html#model/geometry/tangent" }), jsx(BoundProperty, { component: TextureSelectorPropertyLine, label: "Geometry Tangent", target: material, propertyKey: "geometryTangentTexture", scene: material.getScene(), defaultValue: null, onLink: (texture) => void 0 }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Coat Tangent Angle", target: material, propertyKey: "geometryCoatTangentAngle", min: 0, max: Math.PI, step: 0.01, description: "Tangent vector controlling anisotropic reflection direction for the coat. Works with coat_roughness_anisotropy.", docLink: "https://academysoftwarefoundation.github.io/OpenPBR/index.html#model/geometry/coat-tangent" }), jsx(BoundProperty, { component: TextureSelectorPropertyLine, label: "Geometry Coat Normal", target: material, propertyKey: "geometryCoatNormalTexture", scene: material.getScene(), defaultValue: null, onLink: (texture) => void 0 }), jsx(BoundProperty, { component: TextureSelectorPropertyLine, label: "Geometry Coat Tangent", target: material, propertyKey: "geometryCoatTangentTexture", scene: material.getScene(), defaultValue: null, onLink: (texture) => void 0 }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Geometry Thickness", target: material, propertyKey: "geometryThickness", min: 0, step: 0.001, description: "Controls the thickness of the geometry for volume approximations.", docLink: "https://academysoftwarefoundation.github.io/OpenPBR/index.html#model/thickness" }), jsx(BoundProperty, { component: TextureSelectorPropertyLine, label: "Geometry Thickness", target: material, propertyKey: "geometryThicknessTexture", scene: material.getScene(), defaultValue: null, onLink: (texture) => void 0 })] }));
16706
+ return (jsxs(Fragment, { children: [jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Geometry Opacity", target: material, propertyKey: "geometryOpacity", min: 0, max: 1, step: 0.01, description: "Controls material presence and transparency cutout.", docLink: "https://academysoftwarefoundation.github.io/OpenPBR/index.html#model/opacity/transparency" }), jsx(BoundProperty, { component: MaterialTextureDebugPropertyLine, label: "Geometry Opacity", target: material, propertyKey: "geometryOpacityTexture", scene: material.getScene(), defaultValue: null, onLink: (texture) => void 0, material: material }), jsx(BoundProperty, { component: CheckboxPropertyLine, label: "Thin-Walled", target: material, propertyKey: "geometryThinWalled", description: "When enabled, treats material as a thin shell (like leaves, paper sheets or windows). Disables ray bending in refraction.", docLink: "https://academysoftwarefoundation.github.io/OpenPBR/index.html#model/thin-walledcase" }), jsx(BoundProperty, { component: MaterialTextureDebugPropertyLine, label: "Geometry Normal", target: material, propertyKey: "geometryNormalTexture", scene: material.getScene(), defaultValue: null, onLink: (texture) => void 0, material: material }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Tangent Angle", target: material, propertyKey: "geometryTangentAngle", min: 0, max: Math.PI, step: 0.01, description: "Tangent vector controlling anisotropic reflection direction for the base (metal and non-metal). Works with specular_roughness_anisotropy.", docLink: "https://academysoftwarefoundation.github.io/OpenPBR/index.html#model/geometry/tangent" }), jsx(BoundProperty, { component: MaterialTextureDebugPropertyLine, label: "Geometry Tangent", target: material, propertyKey: "geometryTangentTexture", scene: material.getScene(), defaultValue: null, onLink: (texture) => void 0, material: material }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Coat Tangent Angle", target: material, propertyKey: "geometryCoatTangentAngle", min: 0, max: Math.PI, step: 0.01, description: "Tangent vector controlling anisotropic reflection direction for the coat. Works with coat_roughness_anisotropy.", docLink: "https://academysoftwarefoundation.github.io/OpenPBR/index.html#model/geometry/coat-tangent" }), jsx(BoundProperty, { component: MaterialTextureDebugPropertyLine, label: "Geometry Coat Normal", target: material, propertyKey: "geometryCoatNormalTexture", scene: material.getScene(), defaultValue: null, onLink: (texture) => void 0, material: material }), jsx(BoundProperty, { component: MaterialTextureDebugPropertyLine, label: "Geometry Coat Tangent", target: material, propertyKey: "geometryCoatTangentTexture", scene: material.getScene(), defaultValue: null, onLink: (texture) => void 0, material: material }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Geometry Thickness", target: material, propertyKey: "geometryThickness", min: 0, step: 0.001, description: "Controls the thickness of the geometry for volume approximations.", docLink: "https://academysoftwarefoundation.github.io/OpenPBR/index.html#model/thickness" }), jsx(BoundProperty, { component: MaterialTextureDebugPropertyLine, label: "Geometry Thickness", target: material, propertyKey: "geometryThicknessTexture", scene: material.getScene(), defaultValue: null, onLink: (texture) => void 0, material: material })] }));
16037
16707
  };
16038
16708
  const SssQualityOptions = [
16039
16709
  { label: "Low (8 samples)", value: 0 },
@@ -16068,7 +16738,7 @@ const StandardMaterialTexturesProperties = (props) => {
16068
16738
  const { material, selectionService } = props;
16069
16739
  const scene = material.getScene();
16070
16740
  const selectEntity = (entity) => (selectionService.selectedEntity = entity);
16071
- return (jsxs(Fragment, { children: [jsx(BoundProperty, { component: TextureSelectorPropertyLine, label: "Diffuse", target: material, propertyKey: "diffuseTexture", scene: scene, onLink: selectEntity, defaultValue: null }), jsx(BoundProperty, { component: TextureSelectorPropertyLine, label: "Specular", target: material, propertyKey: "specularTexture", scene: scene, onLink: selectEntity, defaultValue: null }), jsx(BoundProperty, { component: TextureSelectorPropertyLine, uniqueId: "StandardMaterialTextures_Reflection", label: "Reflection", target: material, propertyKey: "reflectionTexture", scene: scene, onLink: selectEntity, defaultValue: null }), jsx(BoundProperty, { component: TextureSelectorPropertyLine, label: "Refraction", target: material, propertyKey: "refractionTexture", scene: scene, onLink: selectEntity, defaultValue: null }), jsx(BoundProperty, { component: TextureSelectorPropertyLine, label: "Emissive", target: material, propertyKey: "emissiveTexture", scene: scene, onLink: selectEntity, defaultValue: null }), jsx(BoundProperty, { component: TextureSelectorPropertyLine, label: "Bump", target: material, propertyKey: "bumpTexture", scene: scene, onLink: selectEntity, defaultValue: null }), jsx(BoundProperty, { component: TextureSelectorPropertyLine, label: "Opacity", target: material, propertyKey: "opacityTexture", scene: scene, onLink: selectEntity, defaultValue: null }), jsx(BoundProperty, { component: TextureSelectorPropertyLine, label: "Ambient", target: material, propertyKey: "ambientTexture", scene: scene, onLink: selectEntity, defaultValue: null }), jsx(BoundProperty, { component: TextureSelectorPropertyLine, label: "Lightmap", target: material, propertyKey: "lightmapTexture", scene: scene, onLink: selectEntity, defaultValue: null }), jsx(BoundProperty, { component: TextureSelectorPropertyLine, label: "Detailmap", target: material.detailMap, propertyKey: "texture", propertyPath: "detailMap.texture", scene: scene, onLink: selectEntity, defaultValue: null }), jsx(BoundProperty, { component: SwitchPropertyLine, label: "Use Lightmap as Shadowmap", target: material, propertyKey: "useLightmapAsShadowmap" }), jsx(BoundProperty, { component: SwitchPropertyLine, label: "Use Detailmap", target: material.detailMap, propertyKey: "isEnabled", propertyPath: "detailMap.isEnabled" }), jsx(BoundProperty, { component: SwitchPropertyLine, label: "Use Decalmap", target: material.decalMap, propertyKey: "isEnabled", propertyPath: "decalMap.isEnabled" })] }));
16741
+ return (jsxs(Fragment, { children: [jsx(BoundProperty, { component: MaterialTextureDebugPropertyLine, label: "Diffuse", target: material, propertyKey: "diffuseTexture", scene: scene, onLink: selectEntity, defaultValue: null, material: material }), jsx(BoundProperty, { component: MaterialTextureDebugPropertyLine, label: "Specular", target: material, propertyKey: "specularTexture", scene: scene, onLink: selectEntity, defaultValue: null, material: material }), jsx(BoundProperty, { component: MaterialTextureDebugPropertyLine, uniqueId: "StandardMaterialTextures_Reflection", label: "Reflection", target: material, propertyKey: "reflectionTexture", scene: scene, onLink: selectEntity, defaultValue: null, material: material }), jsx(BoundProperty, { component: MaterialTextureDebugPropertyLine, label: "Refraction", target: material, propertyKey: "refractionTexture", scene: scene, onLink: selectEntity, defaultValue: null, material: material }), jsx(BoundProperty, { component: MaterialTextureDebugPropertyLine, label: "Emissive", target: material, propertyKey: "emissiveTexture", scene: scene, onLink: selectEntity, defaultValue: null, material: material }), jsx(BoundProperty, { component: MaterialTextureDebugPropertyLine, label: "Bump", target: material, propertyKey: "bumpTexture", scene: scene, onLink: selectEntity, defaultValue: null, material: material }), jsx(BoundProperty, { component: MaterialTextureDebugPropertyLine, label: "Opacity", target: material, propertyKey: "opacityTexture", scene: scene, onLink: selectEntity, defaultValue: null, material: material }), jsx(BoundProperty, { component: MaterialTextureDebugPropertyLine, label: "Ambient", target: material, propertyKey: "ambientTexture", scene: scene, onLink: selectEntity, defaultValue: null, material: material }), jsx(BoundProperty, { component: MaterialTextureDebugPropertyLine, label: "Lightmap", target: material, propertyKey: "lightmapTexture", scene: scene, onLink: selectEntity, defaultValue: null, material: material }), jsx(BoundProperty, { component: MaterialTextureDebugPropertyLine, label: "Detailmap", target: material.detailMap, propertyKey: "texture", propertyPath: "detailMap.texture", scene: scene, onLink: selectEntity, defaultValue: null, material: material }), jsx(BoundProperty, { component: SwitchPropertyLine, label: "Use Lightmap as Shadowmap", target: material, propertyKey: "useLightmapAsShadowmap" }), jsx(BoundProperty, { component: SwitchPropertyLine, label: "Use Detailmap", target: material.detailMap, propertyKey: "isEnabled", propertyPath: "detailMap.isEnabled" }), jsx(BoundProperty, { component: SwitchPropertyLine, label: "Use Decalmap", target: material.decalMap, propertyKey: "isEnabled", propertyPath: "decalMap.isEnabled" })] }));
16072
16742
  };
16073
16743
  /**
16074
16744
  * Displays the levels properties of a standard material.
@@ -21525,6 +22195,243 @@ const AtmosphereExplorerServiceDefinition = {
21525
22195
  },
21526
22196
  };
21527
22197
 
22198
+ function IsRoutable(node) {
22199
+ return node instanceof AbstractSoundSource || node instanceof AudioBus;
22200
+ }
22201
+ function GetEngineDisplayName(engine) {
22202
+ return LastCreatedAudioEngine() === engine ? "Last Created Audio Engine" : "Other Audio Engine";
22203
+ }
22204
+ const AudioV2ExplorerServiceDefinition = {
22205
+ friendlyName: "Audio V2 Explorer",
22206
+ consumes: [SceneExplorerServiceIdentity, SceneContextIdentity, GizmoServiceIdentity, SelectionServiceIdentity],
22207
+ factory: (sceneExplorerService, sceneContext, gizmoService, selectionService) => {
22208
+ // Section-level observables driven by per-engine subscriptions below.
22209
+ const entityAddedObservable = new Observable();
22210
+ const entityRemovedObservable = new Observable();
22211
+ const entityMovedObservable = new Observable();
22212
+ // Notified whenever any engine's "Last Created" status may have changed (i.e. an engine
22213
+ // was added or removed). Display info hooks subscribe to this to refresh the engine label.
22214
+ const engineDisplayNameChangedObservable = new Observable();
22215
+ // Per-engine subscription tokens (disposed when the engine is disposed).
22216
+ const engineSubscriptions = new Map();
22217
+ // Per-instance outBus interception tokens for sounds / sound sources / audio buses.
22218
+ const outBusInterceptors = new Map();
22219
+ const subscribeOutBus = (entity) => {
22220
+ if (outBusInterceptors.has(entity)) {
22221
+ return;
22222
+ }
22223
+ outBusInterceptors.set(entity, InterceptProperty(entity, "outBus", {
22224
+ afterSet: () => entityMovedObservable.notifyObservers(entity),
22225
+ }));
22226
+ };
22227
+ const unsubscribeOutBus = (entity) => {
22228
+ outBusInterceptors.get(entity)?.dispose();
22229
+ outBusInterceptors.delete(entity);
22230
+ };
22231
+ const subscribeEngine = (engine) => {
22232
+ if (engineSubscriptions.has(engine)) {
22233
+ return;
22234
+ }
22235
+ const nodeAddedObserver = engine.onNodeAddedObservable.add((node) => {
22236
+ if (IsRoutable(node)) {
22237
+ subscribeOutBus(node);
22238
+ }
22239
+ entityAddedObservable.notifyObservers(node);
22240
+ });
22241
+ const nodeRemovedObserver = engine.onNodeRemovedObservable.add((node) => {
22242
+ if (IsRoutable(node)) {
22243
+ unsubscribeOutBus(node);
22244
+ }
22245
+ entityRemovedObservable.notifyObservers(node);
22246
+ });
22247
+ const disposeObserver = engine.onDisposeObservable.add(() => {
22248
+ unsubscribeEngine(engine);
22249
+ entityRemovedObservable.notifyObservers(engine);
22250
+ engineDisplayNameChangedObservable.notifyObservers();
22251
+ });
22252
+ // Seed outBus interception for nodes that already exist on this engine.
22253
+ for (const node of engine.nodes) {
22254
+ if (IsRoutable(node)) {
22255
+ subscribeOutBus(node);
22256
+ }
22257
+ }
22258
+ engineSubscriptions.set(engine, {
22259
+ dispose: () => {
22260
+ nodeAddedObserver.remove();
22261
+ nodeRemovedObserver.remove();
22262
+ disposeObserver.remove();
22263
+ },
22264
+ });
22265
+ };
22266
+ const unsubscribeEngine = (engine) => {
22267
+ engineSubscriptions.get(engine)?.dispose();
22268
+ engineSubscriptions.delete(engine);
22269
+ };
22270
+ // Seed with engines that already exist.
22271
+ for (const engine of AudioEngineV2.Instances) {
22272
+ subscribeEngine(engine);
22273
+ }
22274
+ // React to new engines.
22275
+ const engineCreatedObserver = OnAudioEngineV2CreatedObservable.add((engine) => {
22276
+ subscribeEngine(engine);
22277
+ entityAddedObservable.notifyObservers(engine);
22278
+ engineDisplayNameChangedObservable.notifyObservers();
22279
+ });
22280
+ const sectionRegistration = sceneExplorerService.addSection({
22281
+ displayName: "Audio V2",
22282
+ order: 1500 /* DefaultSectionsOrder.AudioV2 */,
22283
+ getRootEntities: () => [...AudioEngineV2.Instances],
22284
+ getEntityChildren: (entity) => {
22285
+ if (entity instanceof AudioEngineV2) {
22286
+ const children = [];
22287
+ for (const node of entity.nodes) {
22288
+ if (node instanceof MainAudioBus) {
22289
+ children.push(node);
22290
+ }
22291
+ else if (IsRoutable(node) && !node.outBus) {
22292
+ // Surface routable nodes that don't route through a bus (e.g. microphone
22293
+ // sound sources, or sounds with outBus explicitly cleared) directly under
22294
+ // the engine so they remain visible in the tree.
22295
+ children.push(node);
22296
+ }
22297
+ }
22298
+ return children;
22299
+ }
22300
+ if (entity instanceof MainAudioBus || entity instanceof AudioBus) {
22301
+ const children = [];
22302
+ for (const node of entity.engine.nodes) {
22303
+ if (IsRoutable(node) && node.outBus === entity) {
22304
+ children.push(node);
22305
+ }
22306
+ }
22307
+ return children;
22308
+ }
22309
+ return [];
22310
+ },
22311
+ getEntityDisplayInfo: (entity) => {
22312
+ const onChangeObservable = new Observable();
22313
+ let nodeNameObserver = null;
22314
+ let engineDisplayObserver = null;
22315
+ if (entity instanceof AudioEngineV2) {
22316
+ engineDisplayObserver = engineDisplayNameChangedObservable.add(() => onChangeObservable.notifyObservers());
22317
+ }
22318
+ else {
22319
+ nodeNameObserver = entity.onNameChangedObservable.add(() => onChangeObservable.notifyObservers());
22320
+ }
22321
+ return {
22322
+ get name() {
22323
+ if (entity instanceof AudioEngineV2) {
22324
+ return GetEngineDisplayName(entity);
22325
+ }
22326
+ return entity.name || `Unnamed ${entity.getClassName()}`;
22327
+ },
22328
+ onChange: onChangeObservable,
22329
+ dispose: () => {
22330
+ nodeNameObserver?.remove();
22331
+ engineDisplayObserver?.remove();
22332
+ onChangeObservable.clear();
22333
+ },
22334
+ };
22335
+ },
22336
+ entityIcon: ({ entity }) => {
22337
+ const color = tokens.colorPaletteForestForeground2;
22338
+ if (entity instanceof AudioEngineV2) {
22339
+ return jsx(HeadphonesSoundWaveRegular, { color: color });
22340
+ }
22341
+ if (entity instanceof AbstractAudioBus) {
22342
+ return jsx(ArrowEnterUpRegular, { color: color });
22343
+ }
22344
+ if (entity instanceof StaticSound) {
22345
+ return jsx(SoundWaveCircleRegular, { color: color });
22346
+ }
22347
+ if (entity instanceof StreamingSound) {
22348
+ return jsx(SoundWaveCircleFilled, { color: color });
22349
+ }
22350
+ if (entity instanceof AbstractSoundSource) {
22351
+ return jsx(CatchUpRegular, { color: color });
22352
+ }
22353
+ return jsx(Fragment, {});
22354
+ },
22355
+ getEntityAddedObservables: () => [entityAddedObservable],
22356
+ getEntityRemovedObservables: () => [entityRemovedObservable],
22357
+ getEntityMovedObservables: () => [entityMovedObservable],
22358
+ });
22359
+ // Spatial-audio visualization gizmo toggle.
22360
+ const spatialGizmoCommandRegistration = sceneExplorerService.addEntityCommand({
22361
+ predicate: (entity) => entity instanceof AbstractSoundSource && entity._isSpatial,
22362
+ order: 800 /* DefaultCommandsOrder.GizmoActive */,
22363
+ getCommand: (source) => {
22364
+ const onChangeObservable = new Observable();
22365
+ let gizmoRef = null;
22366
+ let onClickedObserver = null;
22367
+ const releaseGizmo = () => {
22368
+ onClickedObserver?.dispose();
22369
+ onClickedObserver = null;
22370
+ gizmoRef?.dispose();
22371
+ gizmoRef = null;
22372
+ };
22373
+ return {
22374
+ type: "toggle",
22375
+ get displayName() {
22376
+ return `Turn ${gizmoRef ? "Off" : "On"} Gizmo`;
22377
+ },
22378
+ icon: () => (gizmoRef ? jsx(EyeRegular, {}) : jsx(EyeOffRegular, {})),
22379
+ get isEnabled() {
22380
+ return !!gizmoRef;
22381
+ },
22382
+ set isEnabled(enabled) {
22383
+ if (enabled) {
22384
+ if (!gizmoRef) {
22385
+ const scene = sceneContext.currentScene;
22386
+ if (scene) {
22387
+ const ref = gizmoService.getSpatialAudioGizmo(source, scene);
22388
+ gizmoRef = ref;
22389
+ // Clicking the gizmo in the viewport selects the underlying sound source
22390
+ // so the inspector navigates to it (mirrors how camera/light gizmos behave
22391
+ // via the scene picking path).
22392
+ const observer = ref.value.onClickedObservable.add((clickedSource) => {
22393
+ selectionService.selectedEntity = clickedSource;
22394
+ });
22395
+ onClickedObserver = { dispose: () => observer.remove() };
22396
+ onChangeObservable.notifyObservers();
22397
+ }
22398
+ }
22399
+ }
22400
+ else if (gizmoRef) {
22401
+ releaseGizmo();
22402
+ onChangeObservable.notifyObservers();
22403
+ }
22404
+ },
22405
+ onChange: onChangeObservable,
22406
+ dispose: () => {
22407
+ releaseGizmo();
22408
+ onChangeObservable.clear();
22409
+ },
22410
+ };
22411
+ },
22412
+ });
22413
+ return {
22414
+ dispose: () => {
22415
+ engineCreatedObserver.remove();
22416
+ for (const subscription of engineSubscriptions.values()) {
22417
+ subscription.dispose();
22418
+ }
22419
+ engineSubscriptions.clear();
22420
+ for (const token of outBusInterceptors.values()) {
22421
+ token.dispose();
22422
+ }
22423
+ outBusInterceptors.clear();
22424
+ entityAddedObservable.clear();
22425
+ entityRemovedObservable.clear();
22426
+ entityMovedObservable.clear();
22427
+ engineDisplayNameChangedObservable.clear();
22428
+ spatialGizmoCommandRegistration.dispose();
22429
+ sectionRegistration.dispose();
22430
+ },
22431
+ };
22432
+ },
22433
+ };
22434
+
21528
22435
  const DisposableCommandServiceDefinition = {
21529
22436
  friendlyName: "Disposable Command Service",
21530
22437
  consumes: [SceneExplorerServiceIdentity, SceneContextIdentity],
@@ -25288,7 +26195,7 @@ const Dialog = (props) => {
25288
26195
  if (!data.open && onDismiss) {
25289
26196
  onDismiss();
25290
26197
  }
25291
- }, children: jsx(DialogSurface, { children: jsxs(DialogBody, { children: [jsx(DialogTitle, { action: onDismiss ? (jsx(DialogTrigger, { action: "close", children: jsx(Button, { appearance: "subtle", "aria-label": "close", icon: DismissRegular }) })) : undefined, children: title }), jsx(DialogContent, { children: children }), actions && actions.length > 0 && (jsx(DialogActions, { children: actions.map((action, index) => (jsx(Button, { appearance: action.appearance ?? "secondary", onClick: action.onClick, label: action.label }, index))) }))] }) }) }));
26198
+ }, children: jsx(DialogSurface, { children: jsxs(DialogBody, { children: [jsx(DialogTitle, { action: onDismiss ? (jsx(DialogTrigger, { action: "close", children: jsx(Button, { appearance: "subtle", ariaLabel: "close", icon: DismissRegular }) })) : undefined, children: title }), jsx(DialogContent, { children: children }), actions && actions.length > 0 && (jsx(DialogActions, { children: actions.map((action, index) => (jsx(Button, { appearance: action.appearance ?? "secondary", onClick: action.onClick, label: action.label }, index))) }))] }) }) }));
25292
26199
  };
25293
26200
 
25294
26201
  const SmartAssetsPaneKey = "Smart Assets";
@@ -25570,7 +26477,7 @@ function ShowInspector(scene, options = {}) {
25570
26477
  // Helps with managing gizmos and a shared utility layer.
25571
26478
  GizmoServiceDefinition,
25572
26479
  // Scene explorer tab and related services.
25573
- SceneExplorerServiceDefinition, NodeExplorerServiceDefinition, SkeletonExplorerServiceDefinition, MaterialExplorerServiceDefinition, TextureExplorerServiceDefinition, PostProcessExplorerServiceDefinition, RenderingPipelineExplorerServiceDefinition, EffectLayerExplorerServiceDefinition, ParticleSystemExplorerServiceDefinition, SpriteManagerExplorerServiceDefinition, AnimationGroupExplorerServiceDefinition, GuiExplorerServiceDefinition, FrameGraphExplorerServiceDefinition, AtmosphereExplorerServiceDefinition, SoundExplorerServiceDefinition, DisposableCommandServiceDefinition,
26480
+ SceneExplorerServiceDefinition, NodeExplorerServiceDefinition, SkeletonExplorerServiceDefinition, MaterialExplorerServiceDefinition, TextureExplorerServiceDefinition, PostProcessExplorerServiceDefinition, RenderingPipelineExplorerServiceDefinition, EffectLayerExplorerServiceDefinition, ParticleSystemExplorerServiceDefinition, SpriteManagerExplorerServiceDefinition, AnimationGroupExplorerServiceDefinition, GuiExplorerServiceDefinition, FrameGraphExplorerServiceDefinition, AtmosphereExplorerServiceDefinition, SoundExplorerServiceDefinition, AudioV2ExplorerServiceDefinition, DisposableCommandServiceDefinition,
25574
26481
  // Properties pane tab and related services.
25575
26482
  ScenePropertiesServiceDefinition, PropertiesServiceDefinition, TexturePropertiesServiceDefinition, CommonPropertiesServiceDefinition, TransformPropertiesServiceDefinition, AnimationPropertiesServiceDefinition, NodePropertiesServiceDefinition, PhysicsPropertiesServiceDefinition, SkeletonPropertiesServiceDefinition, MaterialPropertiesServiceDefinition, LightPropertiesServiceDefinition, SpritePropertiesServiceDefinition, ParticleSystemPropertiesServiceDefinition, CameraPropertiesServiceDefinition, PostProcessPropertiesServiceDefinition, RenderingPipelinePropertiesServiceDefinition, EffectLayerPropertiesServiceDefinition, FrameGraphPropertiesServiceDefinition, AnimationGroupPropertiesServiceDefinition, MetadataPropertiesServiceDefinition, AtmospherePropertiesServiceDefinition, AudioPropertiesServiceDefinition,
25576
26483
  // Texture editor and related services.
@@ -26442,4 +27349,4 @@ const TextAreaPropertyLine = (props) => {
26442
27349
  AttachDebugLayer();
26443
27350
 
26444
27351
  export { GetPropertyDescriptor as $, Accordion as A, Button as B, CheckboxPropertyLine as C, Color4PropertyLine as D, ColorPickerPopup as E, ColorStepGradientComponent as F, ComboBox as G, ComboBoxPropertyLine as H, ConstructorFactory as I, ConvertOptions as J, DebugServiceIdentity as K, Link as L, MessageBar as M, NumberInputPropertyLine as N, DetachDebugLayer as O, Popover as P, DraggableLine as Q, Dropdown as R, ShellServiceIdentity as S, TextInputPropertyLine as T, EntitySelector as U, Vector3PropertyLine as V, ErrorBoundary as W, ExtensibleAccordion as X, FactorGradientComponent as Y, FactorGradientList as Z, FileUploadLine as _, useToast as a, ThemeServiceIdentity as a$, GizmoServiceIdentity as a0, HexPropertyLine as a1, InfoLabel as a2, InputHexField as a3, InputHsvField as a4, Inspector as a5, InterceptFunction as a6, InterceptProperty as a7, IsPropertyReadonly as a8, LineContainer as a9, SearchBox as aA, SelectionServiceDefinition as aB, SettingsServiceIdentity as aC, SettingsStore as aD, SettingsStoreIdentity as aE, ShowInspector as aF, SidePaneContainer as aG, SkeletonSelector as aH, Slider as aI, SpinButton as aJ, StartInspectable as aK, StatsServiceIdentity as aL, StringDropdown as aM, StringDropdownPropertyLine as aN, StringifiedPropertyLine as aO, Switch as aP, SwitchPropertyLine as aQ, SyncedSliderInput as aR, SyncedSliderPropertyLine as aS, TeachingMoment as aT, TextAreaPropertyLine as aU, TextInput as aV, TextPropertyLine as aW, Textarea as aX, TextureSelector as aY, TextureUpload as aZ, Theme as a_, LinkPropertyLine as aa, LinkToEntityPropertyLine as ab, List as ac, MakeDialogTeachingMoment as ad, MakeLazyComponent as ae, MakeModularBridge as af, MakeModularTool as ag, MakePopoverTeachingMoment as ah, MakePropertyHook as ai, MakeTeachingMoment as aj, MaterialSelector as ak, NodeSelector as al, NumberDropdown as am, NumberDropdownPropertyLine as an, ObservableCollection as ao, Pane as ap, PlaceholderPropertyLine as aq, PositionedPopover as ar, PropertiesServiceIdentity as as, Property as at, PropertyContext as au, PropertyLine as av, QuaternionPropertyLine as aw, RotationVectorPropertyLine as ax, SceneExplorerServiceIdentity as ay, SearchBar as az, useInterceptObservable as b, ToastProvider as b0, ToggleButton as b1, Tooltip as b2, UploadButton as b3, Vector2PropertyLine as b4, Vector4PropertyLine as b5, WatcherServiceIdentity as b6, inspectorAssetNotFoundHandler as b7, useAngleConverters as b8, useAsyncResource as b9, useColor3Property as ba, useColor4Property as bb, useEventListener as bc, useEventfulState as bd, useKeyListener as be, useKeyState as bf, useObservableCollection as bg, useOrderedObservableCollection as bh, usePollingObservable as bi, usePropertyChangedNotifier as bj, useQuaternionProperty as bk, useResource as bl, useTheme as bm, useThemeMode as bn, useVector3Property as bo, LinkToEntity as c, SpinButtonPropertyLine as d, useProperty as e, SceneContextIdentity as f, SelectionServiceIdentity as g, useObservableState as h, AccordionSection as i, ButtonLine as j, ToolsServiceIdentity as k, AccordionSectionItem as l, AttachDebugLayer as m, BooleanBadgePropertyLine as n, BoundProperty as o, BridgeCommandRegistryIdentity as p, BuiltInsExtensionFeed as q, Checkbox as r, ChildWindow as s, Collapse as t, useExtensionManager as u, Color3GradientComponent as v, Color3GradientList as w, Color3PropertyLine as x, Color4GradientComponent as y, Color4GradientList as z };
26445
- //# sourceMappingURL=index-UoPnMkyH.js.map
27352
+ //# sourceMappingURL=index--oJsOVVX.js.map