@babylonjs/inspector 8.49.4 → 8.49.6

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,6 +1,6 @@
1
1
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
2
2
  import { createContext, forwardRef, useContext, useState, useCallback, Component, useMemo, useEffect, useRef, isValidElement, cloneElement, Children, useLayoutEffect, useImperativeHandle, createElement, Suspense, memo, Fragment as Fragment$1, useReducer, lazy } from 'react';
3
- import { tokens, makeStyles, Tooltip as Tooltip$1, Button as Button$1, Spinner, Link as Link$1, Caption1, Body1, ToggleButton as ToggleButton$1, InfoLabel as InfoLabel$1, Body1Strong, Checkbox as Checkbox$1, mergeClasses, Accordion as Accordion$1, AccordionItem, AccordionHeader, Subtitle2Stronger, AccordionPanel, Divider, createLightTheme, createDarkTheme, FluentProvider, TeachingPopover, TeachingPopoverSurface, TeachingPopoverHeader, TeachingPopoverBody, Portal, RendererProvider, createDOMRenderer, Menu, MenuTrigger, SplitButton, MenuPopover, MenuList, MenuItem, Toolbar as Toolbar$1, ToolbarRadioButton, MenuGroup, MenuGroupHeader, treeItemLevelToken, SearchBox as SearchBox$1, FlatTree, FlatTreeItem, TreeItemLayout, MenuDivider, MenuItemCheckbox, Switch as Switch$1, useId, SpinButton as SpinButton$1, Input, Dropdown as Dropdown$1, Option, Popover as Popover$1, PopoverTrigger, PopoverSurface, ColorPicker, ColorArea, ColorSlider, AlphaSlider, ColorSwatch, PresenceBadge, Slider, MenuItemRadio, Dialog, DialogSurface, DialogBody, DialogTitle, DialogContent, DialogActions, List as List$1, ListItem, Badge, Label, MessageBar as MessageBar$1, MessageBarBody, MessageBarTitle, Subtitle2, useComboboxFilter, Combobox, Textarea as Textarea$1, ToolbarButton, ToolbarDivider, Field } from '@fluentui/react-components';
3
+ import { tokens, makeStyles, Tooltip as Tooltip$1, Button as Button$1, Spinner, Link as Link$1, Caption1, Body1, ToggleButton as ToggleButton$1, useFluent, InfoLabel as InfoLabel$1, mergeClasses, Body1Strong, useId, useToastController, Toast, ToastBody, FluentProvider, Toaster, Checkbox as Checkbox$1, Accordion as Accordion$1, AccordionItem, AccordionHeader, Subtitle2Stronger, AccordionPanel, Divider, createLightTheme, createDarkTheme, TeachingPopover, TeachingPopoverSurface, TeachingPopoverHeader, TeachingPopoverBody, Portal, RendererProvider, createDOMRenderer, Menu, MenuTrigger, SplitButton, MenuPopover, MenuList, MenuItem, Toolbar as Toolbar$1, ToolbarRadioButton, MenuGroup, MenuGroupHeader, treeItemLevelToken, SearchBox as SearchBox$1, FlatTree, FlatTreeItem, TreeItemLayout, MenuDivider, MenuItemCheckbox, Switch as Switch$1, SpinButton as SpinButton$1, Input, Dropdown as Dropdown$1, Option, Popover as Popover$1, PopoverTrigger, PopoverSurface, ColorPicker, ColorArea, ColorSlider, AlphaSlider, ColorSwatch, PresenceBadge, Slider, MenuItemRadio, Dialog, DialogSurface, DialogBody, DialogTitle, DialogContent, DialogActions, List as List$1, ListItem, Badge, Label, MessageBar as MessageBar$1, MessageBarBody, MessageBarTitle, Subtitle2, useComboboxFilter, Combobox, Textarea as Textarea$1, ToolbarButton, ToolbarDivider, Field } from '@fluentui/react-components';
4
4
  import { ErrorCircleRegular, ChevronCircleRight16Regular, ChevronCircleRight20Regular, ChevronCircleDown16Regular, ChevronCircleDown20Regular, Copy16Regular, CopyRegular, PanelLeftExpandRegular, PanelRightExpandRegular, PanelLeftContractRegular, PanelRightContractRegular, PictureInPictureEnterRegular, MoreHorizontalRegular, LayoutColumnTwoFocusLeftFilled, LayoutColumnTwoSplitLeftFocusTopLeftFilled, LayoutColumnTwoSplitLeftFocusBottomLeftFilled, LayoutColumnTwoFocusRightFilled, LayoutColumnTwoSplitRightFocusTopRightFilled, LayoutColumnTwoSplitRightFocusBottomRightFilled, DocumentTextRegular, createFluentIcon, FilterRegular, TextSortAscendingRegular, GlobeRegular, ArrowExpandAllRegular, ArrowCollapseAllRegular, CubeTreeRegular, BugRegular, SettingsRegular, ArrowUploadRegular, ArrowDownloadRegular, StopRegular, RecordRegular, DataBarHorizontalRegular, WrenchRegular, WeatherSunnyRegular, WeatherMoonRegular, ArrowRotateClockwiseRegular, ArrowExpandRegular, SelectObjectRegular, CubeRegular, AddRegular, DeleteRegular, FullScreenMaximizeRegular, ChevronDownRegular, ChevronRightRegular, CircleSmallFilled, SaveRegular, PreviousRegular, ArrowPreviousRegular, TriangleLeftRegular, RecordStopRegular, PlayRegular, ArrowNextRegular, NextRegular, EditRegular, PauseRegular, LinkDismissRegular, LinkEditRegular, ArrowUndoRegular, BracesRegular, BracesDismiss16Regular, EyeRegular, CloudArrowUpRegular, CloudArrowDownRegular, EyeOffFilled, EyeFilled, ArrowMoveFilled, StopFilled, PlayFilled, EyeOffRegular, LockOpenRegular, LockClosedRegular, ResizeRegular, ChevronUpRegular, ArrowResetRegular, CircleHalfFillRegular, EyedropperRegular, PaintBucketRegular, InkStrokeRegular, StackRegular, FilmstripRegular, PauseFilled, WeatherSunnyLowFilled, LayerRegular, FrameRegular, AppGenericRegular, RectangleLandscapeRegular, BorderOutsideRegular, BorderNoneRegular, MyLocationRegular, CameraRegular, LightbulbRegular, VideoFilled, VideoRegular, FlashlightRegular, FlashlightOffRegular, DropRegular, BlurRegular, PipelineRegular, PersonWalkingRegular, DataLineRegular, SoundWaveCircleRegular, PersonSquareRegular, LayerDiagonalPersonRegular, ImageEditRegular, ImageRegular, TargetRegular, PersonFeedbackRegular, BranchRegular, DeleteFilled } from '@fluentui/react-icons';
5
5
  import { Color3, Color4 } from '@babylonjs/core/Maths/math.color.js';
6
6
  import { Vector3, Quaternion, Matrix, Vector2, Vector4, TmpVectors } from '@babylonjs/core/Maths/math.vector.js';
@@ -234,13 +234,16 @@ function ValidateColorHex(val) {
234
234
  return val != "" && HEX_REGEX.test(val);
235
235
  }
236
236
 
237
- const Tooltip = (props) => {
237
+ // forwardRef wrapper to avoid "function components cannot be given refs" warning
238
+ // FluentTooltip handles ref forwarding to children internally via applyTriggerPropsToChildren
239
+ const Tooltip = forwardRef((props, _ref) => {
238
240
  const { content, children } = props;
239
241
  if (!content) {
240
242
  return children;
241
243
  }
242
244
  return (jsx(Tooltip$1, { relationship: "description", content: content, children: children }));
243
- };
245
+ });
246
+ Tooltip.displayName = "Tooltip";
244
247
 
245
248
  const useButtonStyles = makeStyles({
246
249
  smallIcon: {
@@ -1094,6 +1097,9 @@ const useInfoLabelStyles = makeStyles({
1094
1097
  overflow: "hidden",
1095
1098
  textOverflow: "ellipsis",
1096
1099
  },
1100
+ copyable: {
1101
+ cursor: "copy",
1102
+ },
1097
1103
  });
1098
1104
  /**
1099
1105
  * Renders a label with an optional popup containing more info
@@ -1103,9 +1109,60 @@ const useInfoLabelStyles = makeStyles({
1103
1109
  const InfoLabel = (props) => {
1104
1110
  InfoLabel.displayName = "InfoLabel";
1105
1111
  const classes = useInfoLabelStyles();
1112
+ const { targetDocument } = useFluent();
1113
+ const [ctrlPressed, setCtrlPressed] = useState(false);
1114
+ useEffect(() => {
1115
+ const targetWindow = targetDocument?.defaultView ?? window;
1116
+ const handleKeyDown = (e) => {
1117
+ if (e.ctrlKey) {
1118
+ setCtrlPressed(true);
1119
+ }
1120
+ };
1121
+ const handleKeyUp = (e) => {
1122
+ if (!e.ctrlKey) {
1123
+ setCtrlPressed(false);
1124
+ }
1125
+ };
1126
+ const handleBlur = () => {
1127
+ setCtrlPressed(false);
1128
+ };
1129
+ targetWindow.addEventListener("keydown", handleKeyDown);
1130
+ targetWindow.addEventListener("keyup", handleKeyUp);
1131
+ targetWindow.addEventListener("blur", handleBlur);
1132
+ return () => {
1133
+ targetWindow.removeEventListener("keydown", handleKeyDown);
1134
+ targetWindow.removeEventListener("keyup", handleKeyUp);
1135
+ targetWindow.removeEventListener("blur", handleBlur);
1136
+ };
1137
+ }, [targetDocument]);
1106
1138
  const infoContent = props.info ? jsx("div", { className: classes.infoPopup, children: props.info }) : undefined;
1107
- return infoContent ? (jsx(InfoLabel$1, { htmlFor: props.htmlFor, info: infoContent, className: props.className, label: props.flexLabel ? { className: classes.labelSlot } : undefined, children: jsx(Body1Strong, { className: classes.labelText, children: props.label }) })) : (jsx(Body1Strong, { className: props.className, children: props.label }));
1139
+ const showCopyCursor = ctrlPressed && props.onContextMenu;
1140
+ // Handle Ctrl+click as context menu action
1141
+ const handleClick = useCallback((e) => {
1142
+ if (e.ctrlKey && props.onContextMenu) {
1143
+ props.onContextMenu(e);
1144
+ }
1145
+ }, [props.onContextMenu]);
1146
+ return infoContent ? (jsx(InfoLabel$1, { htmlFor: props.htmlFor, info: infoContent, className: mergeClasses(props.className, showCopyCursor ? classes.copyable : undefined), label: props.flexLabel ? { className: classes.labelSlot } : undefined, onContextMenu: props.onContextMenu, onClick: handleClick, children: jsx(Body1Strong, { className: classes.labelText, children: props.label }) })) : (jsx(Body1Strong, { className: mergeClasses(props.className, showCopyCursor ? classes.copyable : undefined), onContextMenu: props.onContextMenu, onClick: handleClick, children: props.label }));
1147
+ };
1148
+
1149
+ const ToastContext = createContext({ showToast: () => { } });
1150
+ const ToastProvider = ({ children }) => {
1151
+ const toasterId = useId("toaster");
1152
+ const { dispatchToast } = useToastController(toasterId);
1153
+ const { targetDocument } = useFluent();
1154
+ const showToast = useCallback((message) => {
1155
+ dispatchToast(jsx(Toast, { children: jsx(ToastBody, { children: message }) }), { intent: "success", timeout: 1000 });
1156
+ }, [dispatchToast]);
1157
+ return (jsxs(ToastContext.Provider, { value: { showToast }, children: [children, jsx(FluentProvider, { applyStylesToPortals: true, targetDocument: targetDocument, children: jsx(Toaster, { toasterId: toasterId, position: "bottom-end" }) })] }));
1108
1158
  };
1159
+ /**
1160
+ * Hook to show toast notifications.
1161
+ * @returns Object with showToast function that accepts a message string
1162
+ */
1163
+ function useToast() {
1164
+ return useContext(ToastContext);
1165
+ }
1109
1166
 
1110
1167
  const usePropertyLineStyles = makeStyles({
1111
1168
  baseLine: {
@@ -1170,6 +1227,17 @@ const PropertyLine = forwardRef((props, ref) => {
1170
1227
  const { label, onCopy, expandedContent, children, nullable, ignoreNullable } = props;
1171
1228
  const [expanded, setExpanded] = useState("expandByDefault" in props ? props.expandByDefault : false);
1172
1229
  const cachedVal = useRef(nullable ? props.value : null);
1230
+ const { showToast } = useToast();
1231
+ const handleCopy = useCallback(() => {
1232
+ if (onCopy) {
1233
+ copyCommandToClipboard(onCopy());
1234
+ showToast("Copied property to clipboard");
1235
+ }
1236
+ }, [onCopy, showToast]);
1237
+ const handleContextMenu = useCallback((e) => {
1238
+ e.preventDefault();
1239
+ handleCopy();
1240
+ }, [handleCopy]);
1173
1241
  const description = props.docLink ? jsx(Link, { url: props.docLink, value: props.description ?? "Docs" }) : props.description ? jsx(Body1, { children: props.description }) : undefined;
1174
1242
  // Process children to handle nullable state -- creating component in disabled state with default value in lieu of null value
1175
1243
  const processedChildren = (nullable || ignoreNullable) && isValidElement(children)
@@ -1180,7 +1248,7 @@ const PropertyLine = forwardRef((props, ref) => {
1180
1248
  defaultValue: undefined, // Don't pass defaultValue to children as there is no guarantee how this will be used and we can't mix controlled + uncontrolled state
1181
1249
  })
1182
1250
  : children;
1183
- return (jsxs(LineContainer, { ref: ref, children: [jsxs("div", { className: classes.baseLine, children: [jsx(InfoLabel, { className: classes.infoLabel, htmlFor: "property", info: description, label: label, flexLabel: true }), jsxs("div", { className: classes.rightContent, id: "property", children: [expandedContent && (jsx(ToggleButton, { title: "Expand/Collapse property", appearance: "transparent", checkedIcon: size === "small" ? ChevronCircleDown16Regular : ChevronCircleDown20Regular, uncheckedIcon: size === "small" ? ChevronCircleRight16Regular : ChevronCircleRight20Regular, value: expanded === true, onChange: setExpanded })), nullable && !ignoreNullable && (
1251
+ return (jsxs(LineContainer, { ref: ref, children: [jsxs("div", { className: classes.baseLine, children: [jsx(InfoLabel, { className: classes.infoLabel, htmlFor: "property", info: description, label: label, flexLabel: true, onContextMenu: onCopy ? handleContextMenu : undefined }), jsxs("div", { className: classes.rightContent, id: "property", children: [expandedContent && (jsx(ToggleButton, { title: "Expand/Collapse property", appearance: "transparent", checkedIcon: size === "small" ? ChevronCircleDown16Regular : ChevronCircleDown20Regular, uncheckedIcon: size === "small" ? ChevronCircleRight16Regular : ChevronCircleRight20Regular, value: expanded === true, onChange: setExpanded })), nullable && !ignoreNullable && (
1184
1252
  // If this is a nullableProperty and ignoreNullable was not sent, display a checkbox used to toggle null ('checked' means 'non null')
1185
1253
  jsx(Tooltip, { content: props.value == null ? "Enable property" : "Disable property (set to null)", children: jsx(Checkbox$1, { className: classes.checkbox, indicator: { className: classes.checkboxIndicator }, checked: !(props.value == null), onChange: (_, data) => {
1186
1254
  if (data.checked) {
@@ -1192,7 +1260,7 @@ const PropertyLine = forwardRef((props, ref) => {
1192
1260
  cachedVal.current = props.value;
1193
1261
  props.onChange(null);
1194
1262
  }
1195
- } }) })), jsx("div", { className: classes.childWrapper, children: processedChildren }), onCopy && !disableCopy && (jsx(Tooltip, { content: "Copy to Clipboard", children: jsx(Button, { className: classes.copy, appearance: "transparent", icon: size === "small" ? Copy16Regular : CopyRegular, onClick: () => copyCommandToClipboard(onCopy()) }) }))] })] }), expandedContent && (jsx(Collapse, { visible: !!expanded, children: jsx("div", { className: classes.expandedContentDiv, children: expandedContent }) }))] }));
1263
+ } }) })), jsx("div", { className: classes.childWrapper, children: processedChildren }), onCopy && !disableCopy && (jsx(Tooltip, { content: "Copy to Clipboard", children: jsx(Button, { className: classes.copy, appearance: "transparent", icon: size === "small" ? Copy16Regular : CopyRegular, onClick: handleCopy }) }))] })] }), expandedContent && (jsx(Collapse, { visible: !!expanded, children: jsx("div", { className: classes.expandedContentDiv, children: expandedContent }) }))] }));
1196
1264
  });
1197
1265
  const useLineStyles = makeStyles({
1198
1266
  container: {
@@ -1362,6 +1430,7 @@ const Accordion = forwardRef((props, ref) => {
1362
1430
 
1363
1431
  const CompactModeStorageKey = "Babylon/Settings/IsCompactMode";
1364
1432
  const SidePaneDockOverridesStorageKey = "Babylon/Settings/SidePaneDockOverrides";
1433
+ const DisableCopyStorageKey = "Babylon/Settings/DisableCopy";
1365
1434
  function useSetting(storageKey, defaultValue) {
1366
1435
  const [value, setValue, resetValue] = useLocalStorage(storageKey, defaultValue);
1367
1436
  if (!localStorage.getItem(storageKey)) {
@@ -1376,6 +1445,13 @@ function useSetting(storageKey, defaultValue) {
1376
1445
  function useCompactMode() {
1377
1446
  return useSetting(CompactModeStorageKey, !matchMedia("(pointer: coarse)").matches);
1378
1447
  }
1448
+ /**
1449
+ * Gets the disable copy setting.
1450
+ * @returns A tuple containing the current disable copy value, a function to update it, and a function to reset it.
1451
+ */
1452
+ function useDisableCopy() {
1453
+ return useSetting(DisableCopyStorageKey, false);
1454
+ }
1379
1455
  /**
1380
1456
  * Gets the side pane dock overrides configuration.
1381
1457
  * @returns A record mapping side pane IDs to their dock locations.
@@ -1414,16 +1490,17 @@ function useAngleConverters(settings) {
1414
1490
  return [toDisplayValue, fromDisplayValue, useDegrees];
1415
1491
  }
1416
1492
 
1417
- const CompactModeContextProvider = (props) => {
1493
+ const UXContextProvider = (props) => {
1418
1494
  const [compactMode] = useCompactMode();
1495
+ const [disableCopy] = useDisableCopy();
1419
1496
  const toolsContext = useMemo(() => {
1420
1497
  return {
1421
1498
  toolName: "",
1422
1499
  size: compactMode ? "small" : "medium",
1423
- disableCopy: false,
1500
+ disableCopy,
1424
1501
  useFluent: true,
1425
1502
  };
1426
- }, [compactMode]);
1503
+ }, [compactMode, disableCopy]);
1427
1504
  return jsx(ToolContext.Provider, { value: toolsContext, children: props.children });
1428
1505
  };
1429
1506
 
@@ -1539,7 +1616,7 @@ function ExtensibleAccordion(props) {
1539
1616
  },
1540
1617
  };
1541
1618
  }, []);
1542
- return (jsx("div", { className: classes.rootDiv, children: visibleSections.length > -1 && (jsx(CompactModeContextProvider, { children: jsxs(Accordion, { highlightSections: highlightSections, children: [...visibleSections.map((section) => {
1619
+ return (jsx("div", { className: classes.rootDiv, children: visibleSections.length > -1 && (jsx(UXContextProvider, { children: jsxs(Accordion, { highlightSections: highlightSections, children: [...visibleSections.map((section) => {
1543
1620
  return (jsx(AccordionSection, { title: section.identity, collapseByDefault: section.collapseByDefault, children: section.components }, section.identity));
1544
1621
  })] }) })) }));
1545
1622
  }
@@ -2069,7 +2146,7 @@ const ChildWindow = (props) => {
2069
2146
  flexGrow: 1,
2070
2147
  flexDirection: "column",
2071
2148
  overflow: "hidden",
2072
- }, applyStylesToPortals: false, targetDocument: mountNode.ownerDocument, children: children }) }) }));
2149
+ }, applyStylesToPortals: false, targetDocument: mountNode.ownerDocument, children: jsx(ToastProvider, { children: children }) }) }) }));
2073
2150
  };
2074
2151
 
2075
2152
  // NOTE: This is basically a super simplified version of https://github.com/microsoft/fluentui-contrib/blob/main/packages/react-resize-handle/src/hooks
@@ -3826,6 +3903,7 @@ const SettingsServiceDefinition = {
3826
3903
  const sectionContent = useObservableCollection(sectionContentCollection);
3827
3904
  const scene = useObservableState(() => sceneContext.currentScene, sceneContext.currentSceneObservable);
3828
3905
  const [compactMode, setCompactMode] = useCompactMode();
3906
+ const [disableCopy, setDisableCopy] = useDisableCopy();
3829
3907
  const [, , resetSidePaneLayout] = useSidePaneDockOverrides();
3830
3908
  return (jsx(Fragment, { children: scene && (jsx(ExtensibleAccordion, { sections: sections, sectionContent: sectionContent, context: scene, children: jsxs(AccordionSection, { title: "UI", children: [jsx(SwitchPropertyLine, { label: "Compact Mode", description: "Use a more compact UI with less spacing.", value: compactMode, onChange: (checked) => {
3831
3909
  setCompactMode(checked);
@@ -3837,6 +3915,8 @@ const SettingsServiceDefinition = {
3837
3915
  settings.ignoreBackfacesForPicking = checked;
3838
3916
  } }), jsx(SwitchPropertyLine, { label: "Show Properties on Selection", description: "Shows the Properties pane when an entity is selected.", value: settings.showPropertiesOnEntitySelection, onChange: (checked) => {
3839
3917
  settings.showPropertiesOnEntitySelection = checked;
3918
+ } }), jsx(SwitchPropertyLine, { label: "Disable Copy Button", description: "Disables the copy to clipboard button on property lines. You can still Ctrl+Click on the label to copy.", value: disableCopy, onChange: (checked) => {
3919
+ setDisableCopy(checked);
3840
3920
  } }), jsx(ButtonLine, { label: "Reset Layout", onClick: resetSidePaneLayout })] }) })) }));
3841
3921
  },
3842
3922
  });
@@ -5754,7 +5834,8 @@ const StatsPane = (props) => {
5754
5834
  */
5755
5835
  const BooleanBadgePropertyLine = (props) => {
5756
5836
  BooleanBadgePropertyLine.displayName = "BooleanBadgePropertyLine";
5757
- return (jsx(PropertyLine, { ...props, children: jsx(PresenceBadge, { status: props.value ? "available" : "do-not-disturb", outOfOffice: true }) }));
5837
+ // For now assume BooleanBadge is used for readonly properties and disable copy. In future we could enable with a different copy string
5838
+ return (jsx(PropertyLine, { ...props, onCopy: undefined, children: jsx(PresenceBadge, { status: props.value ? "available" : "do-not-disturb", outOfOffice: true }) }));
5758
5839
  };
5759
5840
 
5760
5841
  const SystemStats = ({ context: scene }) => {
@@ -5905,7 +5986,7 @@ const DefaultInspectorExtensionFeed = new BuiltInsExtensionFeed("Inspector", [
5905
5986
  keywords: ["creation", "tools"],
5906
5987
  ...BabylonWebResources,
5907
5988
  author: { name: "Babylon.js", forumUserName: "" },
5908
- getExtensionModuleAsync: async () => await import('./quickCreateToolsService-BljTQeJt.js'),
5989
+ getExtensionModuleAsync: async () => await import('./quickCreateToolsService-B55TQEXK.js'),
5909
5990
  },
5910
5991
  {
5911
5992
  name: "Reflector",
@@ -5913,7 +5994,7 @@ const DefaultInspectorExtensionFeed = new BuiltInsExtensionFeed("Inspector", [
5913
5994
  keywords: ["reflector", "bridge", "sync", "sandbox", "tools"],
5914
5995
  ...BabylonWebResources,
5915
5996
  author: { name: "Babylon.js", forumUserName: "" },
5916
- getExtensionModuleAsync: async () => await import('./reflectorService-8_EDaWNz.js'),
5997
+ getExtensionModuleAsync: async () => await import('./reflectorService-BHVk1B6L.js'),
5917
5998
  },
5918
5999
  ]);
5919
6000
 
@@ -6743,7 +6824,7 @@ function MakeModularTool(options) {
6743
6824
  });
6744
6825
  // Register the extension list service (for browsing/installing extensions) if extension feeds are provided.
6745
6826
  if (extensionFeeds.length > 0) {
6746
- const { ExtensionListServiceDefinition } = await import('./extensionsListService-DJfDekFP.js');
6827
+ const { ExtensionListServiceDefinition } = await import('./extensionsListService-1OhC4MZF.js');
6747
6828
  await serviceContainer.addServiceAsync(ExtensionListServiceDefinition);
6748
6829
  }
6749
6830
  // Register the theme selector service (for selecting the theme) if theming is configured.
@@ -6816,7 +6897,7 @@ function MakeModularTool(options) {
6816
6897
  // Show a spinner until a main view has been set.
6817
6898
  // eslint-disable-next-line @typescript-eslint/naming-convention
6818
6899
  const Content = rootComponent ?? (() => jsx(Spinner, { className: classes.spinner }));
6819
- return (jsx(ExtensionManagerContext.Provider, { value: extensionManagerContext, children: jsx(Theme, { className: classes.app, children: jsxs(Fragment, { children: [jsx(Dialog, { open: !!requiredExtensions, modalType: "alert", children: jsx(DialogSurface, { children: jsxs(DialogBody, { children: [jsx(DialogTitle, { children: "Required Extensions" }), jsxs(DialogContent, { children: ["Opening this URL requires the following extensions to be installed and enabled:", jsx("ul", { children: requiredExtensions?.map((name) => jsx("li", { children: name }, name)) })] }), jsxs(DialogActions, { children: [jsx(Button$1, { appearance: "primary", onClick: onAcceptRequiredExtensions, children: "Install & Enable" }), jsx(Button$1, { appearance: "secondary", onClick: onRejectRequiredExtensions, children: "No Thanks" })] })] }) }) }), jsx(Dialog, { open: !!extensionInstallError, modalType: "alert", children: jsx(DialogSurface, { children: jsxs(DialogBody, { children: [jsx(DialogTitle, { children: jsxs("div", { className: classes.extensionErrorTitleDiv, children: ["Extension Install Error", jsx(ErrorCircleRegular, { className: classes.extensionErrorIcon })] }) }), jsx(DialogContent, { children: jsxs(List$1, { children: [jsx(ListItem, { children: jsx(Body1, { children: `Extension "${extensionInstallError?.extension.name}" failed to install and was removed.` }) }), jsx(ListItem, { children: jsx(Body1, { children: `${extensionInstallError?.error}` }) })] }) }), jsx(DialogActions, { children: jsx(Button$1, { appearance: "primary", onClick: onAcknowledgedExtensionInstallError, children: "Close" }) })] }) }) }), jsx(Suspense, { fallback: jsx(Spinner, { className: classes.spinner }), children: jsx(Content, {}) })] }) }) }));
6900
+ return (jsx(ExtensionManagerContext.Provider, { value: extensionManagerContext, children: jsx(Theme, { className: classes.app, children: jsxs(ToastProvider, { children: [jsx(Dialog, { open: !!requiredExtensions, modalType: "alert", children: jsx(DialogSurface, { children: jsxs(DialogBody, { children: [jsx(DialogTitle, { children: "Required Extensions" }), jsxs(DialogContent, { children: ["Opening this URL requires the following extensions to be installed and enabled:", jsx("ul", { children: requiredExtensions?.map((name) => jsx("li", { children: name }, name)) })] }), jsxs(DialogActions, { children: [jsx(Button$1, { appearance: "primary", onClick: onAcceptRequiredExtensions, children: "Install & Enable" }), jsx(Button$1, { appearance: "secondary", onClick: onRejectRequiredExtensions, children: "No Thanks" })] })] }) }) }), jsx(Dialog, { open: !!extensionInstallError, modalType: "alert", children: jsx(DialogSurface, { children: jsxs(DialogBody, { children: [jsx(DialogTitle, { children: jsxs("div", { className: classes.extensionErrorTitleDiv, children: ["Extension Install Error", jsx(ErrorCircleRegular, { className: classes.extensionErrorIcon })] }) }), jsx(DialogContent, { children: jsxs(List$1, { children: [jsx(ListItem, { children: jsx(Body1, { children: `Extension "${extensionInstallError?.extension.name}" failed to install and was removed.` }) }), jsx(ListItem, { children: jsx(Body1, { children: `${extensionInstallError?.error}` }) })] }) }), jsx(DialogActions, { children: jsx(Button$1, { appearance: "primary", onClick: onAcknowledgedExtensionInstallError, children: "Close" }) })] }) }) }), jsx(Suspense, { fallback: jsx(Spinner, { className: classes.spinner }), children: jsx(Content, {}) })] }) }) }));
6820
6901
  };
6821
6902
  // Set the container element to be a flex container so that the tool can be displayed properly.
6822
6903
  const originalContainerElementDisplay = containerElement.style.display;
@@ -7776,7 +7857,8 @@ const AnimationList = () => {
7776
7857
  const styles = useStyles$y();
7777
7858
  const { state, observables } = useCurveEditor();
7778
7859
  // Re-render when animations are loaded or changed (e.g. animation deleted)
7779
- useObservableState(() => ({}), observables.onAnimationsLoaded, observables.onActiveAnimationChanged);
7860
+ // useCallback stabilizes the accessor to prevent infinite re-render loops
7861
+ useObservableState(useCallback(() => ({}), []), observables.onAnimationsLoaded, observables.onActiveAnimationChanged);
7780
7862
  // Get animations from target if available (for dynamically added animations), otherwise from state
7781
7863
  const animations = state.target?.animations ?? state.animations;
7782
7864
  return (jsx("div", { className: styles.root, children: animations?.map((a, i) => {
@@ -9300,8 +9382,9 @@ const Graph = ({ width, height }) => {
9300
9382
  const [offsetY, setOffsetY] = useState(0);
9301
9383
  const [isPointerDown, setIsPointerDown] = useState(false);
9302
9384
  const [pointerStart, setPointerStart] = useState({ x: 0, y: 0 });
9303
- // Re-render when active animation or range changes - use counter to invalidate memoized curves
9304
- const animationVersion = useObservableState(() => Date.now(), observables.onActiveAnimationChanged, observables.onRangeUpdated);
9385
+ // Re-render when active animation or range changes
9386
+ // useCallback stabilizes the accessor to prevent infinite re-render loops
9387
+ useObservableState(useCallback(() => ({}), []), observables.onActiveAnimationChanged, observables.onRangeUpdated);
9305
9388
  // Ensure dimensions are valid
9306
9389
  const safeWidth = Math.max(1, width);
9307
9390
  const safeHeight = Math.max(1, height);
@@ -9507,7 +9590,7 @@ const Graph = ({ width, height }) => {
9507
9590
  result.push(...curvesToAdd);
9508
9591
  }
9509
9592
  return result;
9510
- }, [state.activeAnimations, state.activeChannels, animationVersion]);
9593
+ }, [state.activeAnimations, state.activeChannels]);
9511
9594
  // Calculate value range
9512
9595
  const valueRange = useMemo(() => {
9513
9596
  let minValue = 0;
@@ -10042,7 +10125,8 @@ const RangeFrameBar = ({ width }) => {
10042
10125
  const [viewWidth, setViewWidth] = useState(width);
10043
10126
  const [displayFrame, setDisplayFrame] = useState(state.activeFrame);
10044
10127
  // Re-render when range updates
10045
- useObservableState(() => ({}), observables.onRangeUpdated);
10128
+ // useCallback stabilizes the accessor to prevent infinite re-render loops
10129
+ useObservableState(useCallback(() => ({}), []), observables.onRangeUpdated);
10046
10130
  // Update view width on resize
10047
10131
  useEffect(() => {
10048
10132
  const updateWidth = () => {
@@ -10686,7 +10770,7 @@ const CurveEditor = (props) => {
10686
10770
  * @returns The button component
10687
10771
  */
10688
10772
  const CurveEditorButton = (props) => {
10689
- const { scene, target, animations, rootAnimationGroup, title, useTargetAnimations, label = "Edit Curves" } = props;
10773
+ const { scene, target, animations, rootAnimationGroup, title, useTargetAnimations, label = "Open Animation Curve Editor" } = props;
10690
10774
  const childWindow = useRef(null);
10691
10775
  return (jsxs(Fragment, { children: [jsx(ButtonLine, { label: label, onClick: () => childWindow.current?.open({
10692
10776
  defaultWidth: 1024,
@@ -10779,7 +10863,9 @@ const MessageBar = (props) => {
10779
10863
 
10780
10864
  const AnimationsProperties = (props) => {
10781
10865
  const { scene, entity } = props;
10782
- const animations = entity.animations ?? [];
10866
+ // Track animations array changes via property interception
10867
+ const trackedAnimations = useProperty(entity, "animations");
10868
+ const animations = trackedAnimations ?? [];
10783
10869
  const ranges = entity.getAnimationRanges?.()?.filter((range) => !!range) ?? [];
10784
10870
  const childAnimatablesAnimations = entity.getAnimatables?.().flatMap((animatable) => animatable.animations ?? []) ?? [];
10785
10871
  animations.concat(childAnimatablesAnimations);
@@ -10799,40 +10885,40 @@ const AnimationsProperties = (props) => {
10799
10885
  const currentFrame = useObservableState(useCallback(() => {
10800
10886
  return mainAnimatable ? mainAnimatable.masterFrame : (scene.getAllAnimatablesByTarget(entity)[0]?.masterFrame ?? 0);
10801
10887
  }, [scene, entity, mainAnimatable]), hasAnimations ? scene.onAfterAnimationsObservable : undefined);
10802
- return (jsx(Fragment, { children: !hasAnimations ? (jsx(MessageBar, { intent: "info", title: "No Animations", message: "To modify animations, attach an animation to this node.", docLink: "https://doc.babylonjs.com/features/featuresDeepDive/animation/" })) : (jsxs(Fragment, { children: [ranges.length > 0 && (jsx(PropertyLine, { label: "Ranges", expandedContent: jsx(Fragment, { children: ranges.map((range) => {
10803
- return (jsx(ButtonLine, { label: range.name, onClick: () => {
10804
- scene.beginAnimation(entity, range.from, range.to, true);
10805
- } }, range.name));
10806
- }) }), children: jsx(Badge, { appearance: "filled", children: ranges.length }) })), animations.length > 0 && (jsxs(Fragment, { children: [jsx(PropertyLine, { label: "Animations", expandedContent: jsx(Fragment, { children: animations.map((animation, index) => {
10807
- return jsx(TextPropertyLine, { label: `${index}: ${animation.name}`, value: animation.targetProperty }, animation.uniqueId);
10808
- }) }), children: jsx(Badge, { appearance: "filled", children: animations.length }) }), jsx(CurveEditorButton, { scene: scene, target: entity, animations: animations, title: entity.name ?? "Animation Curve Editor" }), mainAnimatable && (jsx(Fragment, { children: jsx(PropertyLine, { label: "Animation Controls", expandedContent: jsxs(Fragment, { children: [jsx(NumberInputPropertyLine, { label: "From", value: mainAnimatable.fromFrame, onChange: (value) => {
10809
- scene.stopAnimation(entity);
10810
- scene.beginAnimation(entity, value, mainAnimatable.toFrame, true);
10811
- } }), jsx(NumberInputPropertyLine, { label: "To", value: mainAnimatable.toFrame, onChange: (value) => {
10812
- scene.stopAnimation(entity);
10813
- scene.beginAnimation(entity, mainAnimatable.fromFrame, value, true);
10814
- } }), jsx(SwitchPropertyLine, { label: "Loop", value: mainAnimatable.loopAnimation, onChange: (value) => {
10815
- for (const animatable of animatablesForTarget) {
10816
- animatable.loopAnimation = value;
10817
- }
10818
- } }), jsx(SyncedSliderPropertyLine, { label: "Current Frame", value: currentFrame, min: mainAnimatable.fromFrame, max: mainAnimatable.toFrame, step: (mainAnimatable.toFrame - mainAnimatable.fromFrame) / 1000, onChange: (value) => {
10819
- mainAnimatable.goToFrame(value);
10820
- } })] }), expandByDefault: true }) })), jsx(ButtonLine, { label: isPlaying ? "Stop Animation" : "Play Animation", onClick: () => {
10821
- if (isPlaying) {
10888
+ return (jsxs(Fragment, { children: [!hasAnimations && (jsx(MessageBar, { intent: "info", title: "No Animations", message: "To modify animations, attach an animation to this node.", docLink: "https://doc.babylonjs.com/features/featuresDeepDive/animation/" })), ranges.length > 0 && (jsx(PropertyLine, { label: "Ranges", expandedContent: jsx(Fragment, { children: ranges.map((range) => {
10889
+ return (jsx(ButtonLine, { label: range.name, onClick: () => {
10890
+ scene.beginAnimation(entity, range.from, range.to, true);
10891
+ } }, range.name));
10892
+ }) }), children: jsx(Badge, { appearance: "filled", children: ranges.length }) })), animations.length > 0 && (jsx(PropertyLine, { label: "Animations", expandedContent: jsx(Fragment, { children: animations.map((animation, index) => {
10893
+ return jsx(TextPropertyLine, { label: `${index}: ${animation.name}`, value: animation.targetProperty }, animation.uniqueId);
10894
+ }) }), children: jsx(Badge, { appearance: "filled", children: animations.length }) })), jsx(CurveEditorButton, { scene: scene, target: entity, animations: animations, title: entity.name ?? "Animation Curve Editor" }), mainAnimatable && (jsx(Fragment, { children: jsx(PropertyLine, { label: "Animation Controls", expandedContent: jsxs(Fragment, { children: [jsx(NumberInputPropertyLine, { label: "From", value: mainAnimatable.fromFrame, onChange: (value) => {
10822
10895
  scene.stopAnimation(entity);
10823
- }
10824
- else {
10825
- scene.beginAnimation(entity, lastFrom.current, lastTo.current, lastLoop.current);
10826
- }
10827
- } }), mainAnimatable && (ranges.length > 0 || animations.length > 0) ? (jsxs(Fragment, { children: [jsx(SwitchPropertyLine, { label: "Enable Override", value: animationPropertiesOverride != null, onChange: (value) => {
10828
- if (value) {
10829
- mainAnimatable.animationPropertiesOverride = new AnimationPropertiesOverride();
10830
- mainAnimatable.animationPropertiesOverride.blendingSpeed = 0.05;
10831
- }
10832
- else {
10833
- mainAnimatable.animationPropertiesOverride = undefined;
10834
- }
10835
- } }), jsx(Collapse, { visible: animationPropertiesOverride != null, children: jsxs("div", { children: [jsx(BoundProperty, { component: SwitchPropertyLine, label: "Enable Blending", target: animationPropertiesOverride, propertyKey: "enableBlending" }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Blending Speed", target: animationPropertiesOverride, propertyKey: "blendingSpeed", min: 0, max: 0.1, step: 0.01 })] }) })] })) : null] }))] })) }));
10896
+ scene.beginAnimation(entity, value, mainAnimatable.toFrame, true);
10897
+ } }), jsx(NumberInputPropertyLine, { label: "To", value: mainAnimatable.toFrame, onChange: (value) => {
10898
+ scene.stopAnimation(entity);
10899
+ scene.beginAnimation(entity, mainAnimatable.fromFrame, value, true);
10900
+ } }), jsx(SwitchPropertyLine, { label: "Loop", value: mainAnimatable.loopAnimation, onChange: (value) => {
10901
+ for (const animatable of animatablesForTarget) {
10902
+ animatable.loopAnimation = value;
10903
+ }
10904
+ } }), jsx(SyncedSliderPropertyLine, { label: "Current Frame", value: currentFrame, min: mainAnimatable.fromFrame, max: mainAnimatable.toFrame, step: (mainAnimatable.toFrame - mainAnimatable.fromFrame) / 1000, onChange: (value) => {
10905
+ mainAnimatable.goToFrame(value);
10906
+ } })] }), expandByDefault: true }) })), hasAnimations && (jsx(ButtonLine, { label: isPlaying ? "Stop Animation" : "Play Animation", onClick: () => {
10907
+ if (isPlaying) {
10908
+ scene.stopAnimation(entity);
10909
+ }
10910
+ else {
10911
+ scene.beginAnimation(entity, lastFrom.current, lastTo.current, lastLoop.current);
10912
+ }
10913
+ } })), mainAnimatable && (ranges.length > 0 || animations.length > 0) ? (jsxs(Fragment, { children: [jsx(SwitchPropertyLine, { label: "Enable Override", value: animationPropertiesOverride != null, onChange: (value) => {
10914
+ if (value) {
10915
+ mainAnimatable.animationPropertiesOverride = new AnimationPropertiesOverride();
10916
+ mainAnimatable.animationPropertiesOverride.blendingSpeed = 0.05;
10917
+ }
10918
+ else {
10919
+ mainAnimatable.animationPropertiesOverride = undefined;
10920
+ }
10921
+ } }), jsx(Collapse, { visible: animationPropertiesOverride != null, children: jsxs("div", { children: [jsx(BoundProperty, { component: SwitchPropertyLine, label: "Enable Blending", target: animationPropertiesOverride, propertyKey: "enableBlending" }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Blending Speed", target: animationPropertiesOverride, propertyKey: "blendingSpeed", min: 0, max: 0.1, step: 0.01 })] }) })] })) : null] }));
10836
10922
  };
10837
10923
 
10838
10924
  function IsAnimatable(entity) {
@@ -13813,6 +13899,8 @@ const ParticleSystemCommandProperties = (props) => {
13813
13899
  const ParticleSystemEmitterProperties = (props) => {
13814
13900
  const { particleSystem: system, selectionService } = props;
13815
13901
  const scene = system.getScene();
13902
+ // Node-generated particle systems don't expose emitter type configuration
13903
+ const isNodeGenerated = system instanceof ParticleSystem && system.isNodeGenerated;
13816
13904
  const emitter = useProperty(system, "emitter");
13817
13905
  const emitterObject = emitter && !(emitter instanceof Vector3) ? emitter : undefined;
13818
13906
  const [sceneNodesVersion, setSceneNodesVersion] = useState(0);
@@ -13928,59 +14016,59 @@ const ParticleSystemEmitterProperties = (props) => {
13928
14016
  else {
13929
14017
  system.emitter = value;
13930
14018
  }
13931
- } })), emitterSelectionValue !== "none" && emitter && !(emitter instanceof Vector3) && (jsx(Property, { component: LinkToEntityPropertyLine, label: "Entity", propertyPath: "emitter", entity: emitter, selectionService: selectionService })), jsx(Property, { component: StringDropdownPropertyLine, label: "Type", propertyPath: "emitterType", value: emitterTypeKey, options: [
13932
- { label: "Box", value: "box" },
13933
- { label: "Cone", value: "cone" },
13934
- { label: "Cylinder", value: "cylinder" },
13935
- { label: "Hemispheric", value: "hemispheric" },
13936
- { label: "Point", value: "point" },
13937
- { label: "Mesh", value: "mesh" },
13938
- { label: "Sphere", value: "sphere" },
13939
- ], onChange: (value) => {
13940
- const next = value;
13941
- setEmitterTypeKey(next);
13942
- // Update the engine by swapping the particleEmitterType instance to match the selected key.
13943
- switch (next) {
13944
- case "box":
13945
- system.createBoxEmitter(new Vector3(0, 1, 0), new Vector3(0, 1, 0), new Vector3(-0.5, -0.5, -0.5), new Vector3(0.5, 0.5, 0.5));
13946
- break;
13947
- case "sphere":
13948
- system.createSphereEmitter(1, 1);
13949
- break;
13950
- case "cone":
13951
- system.createConeEmitter(1, Math.PI / 4);
13952
- break;
13953
- case "cylinder":
13954
- system.createCylinderEmitter(1, 1, 1, 0);
13955
- break;
13956
- case "hemispheric":
13957
- system.createHemisphericEmitter(1, 1);
13958
- break;
13959
- case "point":
13960
- system.createPointEmitter(new Vector3(0, 1, 0), new Vector3(0, 1, 0));
13961
- break;
13962
- case "mesh": {
13963
- // Default to the first mesh in the scene when available, then allow changes via "Source".
13964
- const defaultMesh = scene?.meshes?.[0] ?? null;
13965
- system.particleEmitterType = new MeshParticleEmitter(defaultMesh);
13966
- break;
13967
- }
13968
- }
13969
- } }), particleEmitterType instanceof MeshParticleEmitter && (jsx(Fragment, { children: scene && scene.meshes.length > 0 ? (jsx(Property, { component: StringDropdownPropertyLine, propertyPath: "source", label: "Source", value: particleEmitterType.mesh ? `mesh:${particleEmitterType.mesh.uniqueId}` : `mesh:${scene.meshes[0].uniqueId}`, options: scene.meshes.map((mesh) => {
13970
- const uniqueId = mesh.uniqueId;
13971
- const name = mesh.name ?? "(unnamed)";
13972
- const label = `${name} (#${uniqueId})`;
13973
- return {
13974
- label,
13975
- value: `mesh:${uniqueId}`,
13976
- };
13977
- }), onChange: (value) => {
13978
- const next = String(value);
13979
- const uniqueIdText = next.replace("mesh:", "");
13980
- const uniqueId = Number(uniqueIdText);
13981
- const mesh = scene.meshes.find((candidate) => candidate.uniqueId === uniqueId) ?? null;
13982
- particleEmitterType.mesh = mesh;
13983
- } })) : (jsx(Property, { component: TextPropertyLine, propertyPath: "source", label: "Source", value: "No meshes in scene." })) })), particleEmitterType instanceof BoxParticleEmitter && (jsxs(Fragment, { children: [jsx(BoundProperty, { component: Vector3PropertyLine, label: "Direction1", target: particleEmitterType, propertyKey: "direction1" }), jsx(BoundProperty, { component: Vector3PropertyLine, label: "Direction2", target: particleEmitterType, propertyKey: "direction2" }), jsx(BoundProperty, { component: Vector3PropertyLine, label: "Min emit box", target: particleEmitterType, propertyKey: "minEmitBox" }), jsx(BoundProperty, { component: Vector3PropertyLine, label: "Max emit box", target: particleEmitterType, propertyKey: "maxEmitBox" })] })), particleEmitterType instanceof ConeParticleEmitter && (jsxs(Fragment, { children: [jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Radius range", target: particleEmitterType, propertyKey: "radiusRange", min: 0, max: 1, step: 0.01 }), jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Height range", target: particleEmitterType, propertyKey: "heightRange", min: 0, max: 1, step: 0.01 }), jsx(BoundProperty, { component: SwitchPropertyLine, label: "Emit from spawn point only", target: particleEmitterType, propertyKey: "emitFromSpawnPointOnly" }), jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Direction randomizer", target: particleEmitterType, propertyKey: "directionRandomizer", min: 0, max: 1, step: 0.01 })] })), particleEmitterType instanceof SphereParticleEmitter && (jsxs(Fragment, { children: [jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Radius", target: particleEmitterType, propertyKey: "radius", min: 0, step: 0.1 }), jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Radius range", target: particleEmitterType, propertyKey: "radiusRange", min: 0, max: 1, step: 0.01 }), jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Direction randomizer", target: particleEmitterType, propertyKey: "directionRandomizer", min: 0, max: 1, step: 0.01 })] })), particleEmitterType instanceof CylinderParticleEmitter && (jsxs(Fragment, { children: [jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Radius", target: particleEmitterType, propertyKey: "radius", min: 0, step: 0.1 }), jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Height", target: particleEmitterType, propertyKey: "height", min: 0, step: 0.1 }), jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Radius range", target: particleEmitterType, propertyKey: "radiusRange", min: 0, max: 1, step: 0.01 }), jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Direction randomizer", target: particleEmitterType, propertyKey: "directionRandomizer", min: 0, max: 1, step: 0.01 })] })), particleEmitterType instanceof HemisphericParticleEmitter && (jsxs(Fragment, { children: [jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Radius", target: particleEmitterType, propertyKey: "radius", min: 0, step: 0.1 }), jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Radius range", target: particleEmitterType, propertyKey: "radiusRange", min: 0, max: 1, step: 0.01 }), jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Direction randomizer", target: particleEmitterType, propertyKey: "directionRandomizer", min: 0, max: 1, step: 0.01 })] })), particleEmitterType instanceof PointParticleEmitter && (jsxs(Fragment, { children: [jsx(BoundProperty, { component: Vector3PropertyLine, label: "Direction1", target: particleEmitterType, propertyKey: "direction1" }), jsx(BoundProperty, { component: Vector3PropertyLine, label: "Direction2", target: particleEmitterType, propertyKey: "direction2" })] })), !scene && jsx(TextPropertyLine, { label: "Emitter", value: "No scene available." })] }));
14019
+ } })), emitterSelectionValue !== "none" && emitter && !(emitter instanceof Vector3) && (jsx(Property, { component: LinkToEntityPropertyLine, label: "Entity", propertyPath: "emitter", entity: emitter, selectionService: selectionService })), !isNodeGenerated && (jsxs(Fragment, { children: [jsx(Property, { component: StringDropdownPropertyLine, label: "Type", propertyPath: "emitterType", value: emitterTypeKey, options: [
14020
+ { label: "Box", value: "box" },
14021
+ { label: "Cone", value: "cone" },
14022
+ { label: "Cylinder", value: "cylinder" },
14023
+ { label: "Hemispheric", value: "hemispheric" },
14024
+ { label: "Point", value: "point" },
14025
+ { label: "Mesh", value: "mesh" },
14026
+ { label: "Sphere", value: "sphere" },
14027
+ ], onChange: (value) => {
14028
+ const next = value;
14029
+ setEmitterTypeKey(next);
14030
+ // Update the engine by swapping the particleEmitterType instance to match the selected key.
14031
+ switch (next) {
14032
+ case "box":
14033
+ system.createBoxEmitter(new Vector3(0, 1, 0), new Vector3(0, 1, 0), new Vector3(-0.5, -0.5, -0.5), new Vector3(0.5, 0.5, 0.5));
14034
+ break;
14035
+ case "sphere":
14036
+ system.createSphereEmitter(1, 1);
14037
+ break;
14038
+ case "cone":
14039
+ system.createConeEmitter(1, Math.PI / 4);
14040
+ break;
14041
+ case "cylinder":
14042
+ system.createCylinderEmitter(1, 1, 1, 0);
14043
+ break;
14044
+ case "hemispheric":
14045
+ system.createHemisphericEmitter(1, 1);
14046
+ break;
14047
+ case "point":
14048
+ system.createPointEmitter(new Vector3(0, 1, 0), new Vector3(0, 1, 0));
14049
+ break;
14050
+ case "mesh": {
14051
+ // Default to the first mesh in the scene when available, then allow changes via "Source".
14052
+ const defaultMesh = scene?.meshes?.[0] ?? null;
14053
+ system.particleEmitterType = new MeshParticleEmitter(defaultMesh);
14054
+ break;
14055
+ }
14056
+ }
14057
+ } }), particleEmitterType instanceof MeshParticleEmitter && (jsx(Fragment, { children: scene && scene.meshes.length > 0 ? (jsx(Property, { component: StringDropdownPropertyLine, propertyPath: "source", label: "Source", value: particleEmitterType.mesh ? `mesh:${particleEmitterType.mesh.uniqueId}` : `mesh:${scene.meshes[0].uniqueId}`, options: scene.meshes.map((mesh) => {
14058
+ const uniqueId = mesh.uniqueId;
14059
+ const name = mesh.name ?? "(unnamed)";
14060
+ const label = `${name} (#${uniqueId})`;
14061
+ return {
14062
+ label,
14063
+ value: `mesh:${uniqueId}`,
14064
+ };
14065
+ }), onChange: (value) => {
14066
+ const next = String(value);
14067
+ const uniqueIdText = next.replace("mesh:", "");
14068
+ const uniqueId = Number(uniqueIdText);
14069
+ const mesh = scene.meshes.find((candidate) => candidate.uniqueId === uniqueId) ?? null;
14070
+ particleEmitterType.mesh = mesh;
14071
+ } })) : (jsx(Property, { component: TextPropertyLine, propertyPath: "source", label: "Source", value: "No meshes in scene." })) })), particleEmitterType instanceof BoxParticleEmitter && (jsxs(Fragment, { children: [jsx(BoundProperty, { component: Vector3PropertyLine, label: "Direction1", target: particleEmitterType, propertyKey: "direction1" }), jsx(BoundProperty, { component: Vector3PropertyLine, label: "Direction2", target: particleEmitterType, propertyKey: "direction2" }), jsx(BoundProperty, { component: Vector3PropertyLine, label: "Min emit box", target: particleEmitterType, propertyKey: "minEmitBox" }), jsx(BoundProperty, { component: Vector3PropertyLine, label: "Max emit box", target: particleEmitterType, propertyKey: "maxEmitBox" })] })), particleEmitterType instanceof ConeParticleEmitter && (jsxs(Fragment, { children: [jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Radius range", target: particleEmitterType, propertyKey: "radiusRange", min: 0, max: 1, step: 0.01 }), jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Height range", target: particleEmitterType, propertyKey: "heightRange", min: 0, max: 1, step: 0.01 }), jsx(BoundProperty, { component: SwitchPropertyLine, label: "Emit from spawn point only", target: particleEmitterType, propertyKey: "emitFromSpawnPointOnly" }), jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Direction randomizer", target: particleEmitterType, propertyKey: "directionRandomizer", min: 0, max: 1, step: 0.01 })] })), particleEmitterType instanceof SphereParticleEmitter && (jsxs(Fragment, { children: [jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Radius", target: particleEmitterType, propertyKey: "radius", min: 0, step: 0.1 }), jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Radius range", target: particleEmitterType, propertyKey: "radiusRange", min: 0, max: 1, step: 0.01 }), jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Direction randomizer", target: particleEmitterType, propertyKey: "directionRandomizer", min: 0, max: 1, step: 0.01 })] })), particleEmitterType instanceof CylinderParticleEmitter && (jsxs(Fragment, { children: [jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Radius", target: particleEmitterType, propertyKey: "radius", min: 0, step: 0.1 }), jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Height", target: particleEmitterType, propertyKey: "height", min: 0, step: 0.1 }), jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Radius range", target: particleEmitterType, propertyKey: "radiusRange", min: 0, max: 1, step: 0.01 }), jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Direction randomizer", target: particleEmitterType, propertyKey: "directionRandomizer", min: 0, max: 1, step: 0.01 })] })), particleEmitterType instanceof HemisphericParticleEmitter && (jsxs(Fragment, { children: [jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Radius", target: particleEmitterType, propertyKey: "radius", min: 0, step: 0.1 }), jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Radius range", target: particleEmitterType, propertyKey: "radiusRange", min: 0, max: 1, step: 0.01 }), jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Direction randomizer", target: particleEmitterType, propertyKey: "directionRandomizer", min: 0, max: 1, step: 0.01 })] })), particleEmitterType instanceof PointParticleEmitter && (jsxs(Fragment, { children: [jsx(BoundProperty, { component: Vector3PropertyLine, label: "Direction1", target: particleEmitterType, propertyKey: "direction1" }), jsx(BoundProperty, { component: Vector3PropertyLine, label: "Direction2", target: particleEmitterType, propertyKey: "direction2" })] }))] })), !scene && jsx(TextPropertyLine, { label: "Emitter", value: "No scene available." })] }));
13984
14072
  };
13985
14073
 
13986
14074
  /**
@@ -14702,7 +14790,7 @@ const ParticleSystemPropertiesServiceDefinition = {
14702
14790
  });
14703
14791
  const particleSystemEmitterContent = propertiesService.addSectionContent({
14704
14792
  key: "Particle System Emitter Properties",
14705
- predicate: IsNonNodeParticleSystem,
14793
+ predicate: IsParticleSystem,
14706
14794
  content: [
14707
14795
  {
14708
14796
  section: "Emitter",
@@ -14782,7 +14870,7 @@ const ParticleSystemPropertiesServiceDefinition = {
14782
14870
  },
14783
14871
  ],
14784
14872
  });
14785
- const particleSystemNodeContent = propertiesService.addSectionContent({
14873
+ const particleSystemNodeEditorContent = propertiesService.addSectionContent({
14786
14874
  key: "Node Particle System Inputs Properties",
14787
14875
  predicate: IsNodeParticleSystem,
14788
14876
  content: [
@@ -14805,7 +14893,7 @@ const ParticleSystemPropertiesServiceDefinition = {
14805
14893
  particleSystemColorContent.dispose();
14806
14894
  particleSystemRotationContent.dispose();
14807
14895
  particleSystemSpritesheetContent.dispose();
14808
- particleSystemNodeContent.dispose();
14896
+ particleSystemNodeEditorContent.dispose();
14809
14897
  },
14810
14898
  };
14811
14899
  },
@@ -20535,5 +20623,5 @@ const TextAreaPropertyLine = (props) => {
20535
20623
  // Attach Inspector v2 to Scene.debugLayer as a side effect for back compat.
20536
20624
  AttachDebugLayer();
20537
20625
 
20538
- export { MakeTeachingMoment as $, Accordion as A, Button as B, CheckboxPropertyLine as C, DebugServiceIdentity as D, ExtensibleAccordion as E, useColor3Property as F, useColor4Property as G, useQuaternionProperty as H, Inspector as I, MakePropertyHook as J, useInterceptObservable as K, Link as L, MessageBar as M, NumberInputPropertyLine as N, useEventfulState as O, Popover as P, useObservableCollection as Q, useOrderedObservableCollection as R, SpinButtonPropertyLine as S, TextInputPropertyLine as T, usePollingObservable as U, Vector3PropertyLine as V, useResource as W, useAsyncResource as X, useCompactMode as Y, useSidePaneDockOverrides as Z, useAngleConverters as _, ShellServiceIdentity as a, MakeDialogTeachingMoment as a0, InterceptFunction as a1, GetPropertyDescriptor as a2, IsPropertyReadonly as a3, InterceptProperty as a4, ObservableCollection as a5, ConstructorFactory as a6, SelectionServiceIdentity as a7, SelectionServiceDefinition as a8, SettingsContextIdentity as a9, ToggleButton as aA, ChildWindow as aB, FileUploadLine as aC, FactorGradientList as aD, Color3GradientList as aE, Color4GradientList as aF, Pane as aG, BooleanBadgePropertyLine as aH, Color3PropertyLine as aI, Color4PropertyLine as aJ, HexPropertyLine as aK, LinkPropertyLine as aL, PropertyLine as aM, LineContainer as aN, PlaceholderPropertyLine as aO, StringifiedPropertyLine as aP, SwitchPropertyLine as aQ, SyncedSliderPropertyLine as aR, TextAreaPropertyLine as aS, TextPropertyLine as aT, RotationVectorPropertyLine as aU, QuaternionPropertyLine as aV, Vector2PropertyLine as aW, Vector4PropertyLine as aX, ShowInspector as aa, Checkbox as ab, Collapse as ac, ColorPickerPopup as ad, InputHexField as ae, InputHsvField as af, ComboBox as ag, DraggableLine as ah, Dropdown as ai, NumberDropdown as aj, StringDropdown as ak, FactorGradientComponent as al, Color3GradientComponent as am, Color4GradientComponent as an, ColorStepGradientComponent as ao, InfoLabel as ap, MakeLazyComponent as aq, List as ar, PositionedPopover as as, SearchBar as at, SearchBox as au, SpinButton as av, Switch as aw, SyncedSliderInput as ax, Textarea as ay, TextInput as az, SceneContextIdentity as b, useObservableState as c, AccordionSection as d, ButtonLine as e, ToolsServiceIdentity as f, useExtensionManager as g, MakePopoverTeachingMoment as h, TeachingMoment as i, SidePaneContainer as j, PropertiesServiceIdentity as k, SceneExplorerServiceIdentity as l, SettingsServiceIdentity as m, StatsServiceIdentity as n, ConvertOptions as o, AttachDebugLayer as p, DetachDebugLayer as q, NumberDropdownPropertyLine as r, StringDropdownPropertyLine as s, BoundProperty as t, useProperty as u, Property as v, LinkToEntityPropertyLine as w, Theme as x, BuiltInsExtensionFeed as y, useVector3Property as z };
20539
- //# sourceMappingURL=index-CyX6FYTL.js.map
20626
+ export { useAngleConverters as $, Accordion as A, Button as B, CheckboxPropertyLine as C, DebugServiceIdentity as D, ExtensibleAccordion as E, useColor3Property as F, useColor4Property as G, useQuaternionProperty as H, Inspector as I, MakePropertyHook as J, useInterceptObservable as K, Link as L, MessageBar as M, NumberInputPropertyLine as N, useEventfulState as O, Popover as P, useObservableCollection as Q, useOrderedObservableCollection as R, SpinButtonPropertyLine as S, TextInputPropertyLine as T, usePollingObservable as U, Vector3PropertyLine as V, useResource as W, useAsyncResource as X, useCompactMode as Y, useDisableCopy as Z, useSidePaneDockOverrides as _, ShellServiceIdentity as a, MakeTeachingMoment as a0, MakeDialogTeachingMoment as a1, InterceptFunction as a2, GetPropertyDescriptor as a3, IsPropertyReadonly as a4, InterceptProperty as a5, ObservableCollection as a6, ConstructorFactory as a7, SelectionServiceIdentity as a8, SelectionServiceDefinition as a9, TextInput as aA, ToggleButton as aB, ChildWindow as aC, FileUploadLine as aD, FactorGradientList as aE, Color3GradientList as aF, Color4GradientList as aG, Pane as aH, BooleanBadgePropertyLine as aI, Color3PropertyLine as aJ, Color4PropertyLine as aK, HexPropertyLine as aL, LinkPropertyLine as aM, PropertyLine as aN, LineContainer as aO, PlaceholderPropertyLine as aP, StringifiedPropertyLine as aQ, SwitchPropertyLine as aR, SyncedSliderPropertyLine as aS, TextAreaPropertyLine as aT, TextPropertyLine as aU, RotationVectorPropertyLine as aV, QuaternionPropertyLine as aW, Vector2PropertyLine as aX, Vector4PropertyLine as aY, SettingsContextIdentity as aa, ShowInspector as ab, Checkbox as ac, Collapse as ad, ColorPickerPopup as ae, InputHexField as af, InputHsvField as ag, ComboBox as ah, DraggableLine as ai, Dropdown as aj, NumberDropdown as ak, StringDropdown as al, FactorGradientComponent as am, Color3GradientComponent as an, Color4GradientComponent as ao, ColorStepGradientComponent as ap, InfoLabel as aq, MakeLazyComponent as ar, List as as, PositionedPopover as at, SearchBar as au, SearchBox as av, SpinButton as aw, Switch as ax, SyncedSliderInput as ay, Textarea as az, SceneContextIdentity as b, useObservableState as c, AccordionSection as d, ButtonLine as e, ToolsServiceIdentity as f, useExtensionManager as g, MakePopoverTeachingMoment as h, TeachingMoment as i, SidePaneContainer as j, PropertiesServiceIdentity as k, SceneExplorerServiceIdentity as l, SettingsServiceIdentity as m, StatsServiceIdentity as n, ConvertOptions as o, AttachDebugLayer as p, DetachDebugLayer as q, NumberDropdownPropertyLine as r, StringDropdownPropertyLine as s, BoundProperty as t, useProperty as u, Property as v, LinkToEntityPropertyLine as w, Theme as x, BuiltInsExtensionFeed as y, useVector3Property as z };
20627
+ //# sourceMappingURL=index-ByfjmUIP.js.map