@procaaso/alphinity-ui-components 1.8.1 → 1.8.3

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.
package/dist/index.d.cts CHANGED
@@ -662,7 +662,16 @@ interface DeviceControlPanelProps {
662
662
  /** Embedded mode - uses static positioning for rendering in containers (default: false) */
663
663
  embedded?: boolean;
664
664
  }
665
- declare function DeviceControlPanel({ binding, onClose, position, align, touchOptimized, compact, fontScale, maxHeight, maxWidth, toastDuration, onModeChange, onParameterChange, onParameterActivate, onStart, onStop, startPending, stopPending, onCustomAction, onOpenConfig, showConfigButton, onUndock, undockIcon, modeSelectionStyle, embedded, }: DeviceControlPanelProps): react_jsx_runtime.JSX.Element | null;
665
+ /**
666
+ * Memoized below (kiosk heap-leak fix, same rationale as DeviceConfigPanel):
667
+ * an OPEN control panel must not re-render on host ticks whose props did not
668
+ * change. Unlike the config panel, a control binding legitimately changes when
669
+ * its live values move, so the memo bails only on genuinely idle ticks - the
670
+ * win is proportional to how often values are flat at display precision.
671
+ */
672
+ declare function DeviceControlPanelImpl({ binding, onClose, position, align, touchOptimized, compact, fontScale, maxHeight, maxWidth, toastDuration, onModeChange, onParameterChange, onParameterActivate, onStart, onStop, startPending, stopPending, onCustomAction, onOpenConfig, showConfigButton, onUndock, undockIcon, modeSelectionStyle, embedded, }: DeviceControlPanelProps): react_jsx_runtime.JSX.Element | null;
673
+ /** Memo boundary - see DeviceControlPanelImpl doc comment (kiosk heap-leak fix). */
674
+ declare const DeviceControlPanel: React__default.MemoExoticComponent<typeof DeviceControlPanelImpl>;
666
675
 
667
676
  interface NumpadDialogProps {
668
677
  /** Whether the dialog is open */
@@ -1481,9 +1490,22 @@ interface PIDCanvasControls {
1481
1490
  * - Renders nodes (PR #3) and pipes (PR #4) with static layout
1482
1491
  * - Drag-and-drop: Drag nodes to reposition (PR #8)
1483
1492
  *
1493
+ * Memoized (renderer-OOM fix): a host whose screen re-renders for reasons
1494
+ * unrelated to the canvas (panel state, connection status, ...) should not pay
1495
+ * a full-canvas reconciliation. The memo only pays off when the host passes
1496
+ * identity-stable props (memoized features/telemetry/style, useCallback
1497
+ * handlers); an unstable host degrades gracefully to the old behavior.
1498
+ *
1484
1499
  * Future: Editing tools, symbol library UI
1485
1500
  */
1486
- declare function PIDCanvas({ diagram, className, style, features, onSelectionChange, onNodeClick, onPipeClick, onNodeMove, onDiagramChange, onDelete, onItemsCopied, onPaste, onSymbolDrop, onPipeCreated, onMounted, telemetry, statusColorMap, ...props }: PIDCanvasProps): React__default.ReactElement;
1501
+ declare function PIDCanvasImpl({ diagram, className, style, features, onSelectionChange, onNodeClick, onPipeClick, onNodeMove, onDiagramChange, onDelete, onItemsCopied, onPaste, onSymbolDrop, onPipeCreated, onMounted, telemetry, statusColorMap, ...props }: PIDCanvasProps): React__default.ReactElement;
1502
+ /**
1503
+ * Memo boundary - see PIDCanvasImpl doc comment (renderer-OOM fix). Shallow
1504
+ * compare: `diagram`, `telemetry`, `features`, `style`, `statusColorMap`, and
1505
+ * every callback are compared by identity; hosts that keep them stable skip
1506
+ * the full-canvas re-render entirely when unrelated screen state changes.
1507
+ */
1508
+ declare const PIDCanvas: React__default.MemoExoticComponent<typeof PIDCanvasImpl>;
1487
1509
 
1488
1510
  interface SymbolLibraryProps {
1489
1511
  /** Optional className for styling */
@@ -1957,7 +1979,19 @@ interface DeviceConfigPanelProps {
1957
1979
  /** Embedded mode - uses static positioning for rendering in containers (default: false) */
1958
1980
  embedded?: boolean;
1959
1981
  }
1960
- declare function DeviceConfigPanel({ binding, onClose, onUndock, undockIcon, position, align, touchOptimized, compact, fontScale, maxHeight, maxWidth, toastDuration, onOpenControls, showControlsButton, embedded, }: DeviceConfigPanelProps): react_jsx_runtime.JSX.Element | null;
1982
+ /**
1983
+ * Memoized below (kiosk heap-leak fix): a host whose screen re-renders ~1Hz on
1984
+ * telemetry must not re-render an OPEN config panel every tick - each pass
1985
+ * re-creates the panel's handler closures and re-binds its MUI event handlers,
1986
+ * and on the kiosk those generations were RETAINED (~100 closures/sec,
1987
+ * ~2.1 MB/min heap growth -> renderer OOM at the 512 MB cap, heap-snapshot
1988
+ * fingerprint: Context/scope buckets + onSave/onReset closures). The memo only
1989
+ * pays off when the host passes identity-stable `binding` and handler props;
1990
+ * an unstable host degrades gracefully to the old behavior.
1991
+ */
1992
+ declare function DeviceConfigPanelImpl({ binding, onClose, onUndock, undockIcon, position, align, touchOptimized, compact, fontScale, maxHeight, maxWidth, toastDuration, onOpenControls, showControlsButton, embedded, }: DeviceConfigPanelProps): react_jsx_runtime.JSX.Element | null;
1993
+ /** Memo boundary - see DeviceConfigPanelImpl doc comment (kiosk heap-leak fix). */
1994
+ declare const DeviceConfigPanel: React__default.MemoExoticComponent<typeof DeviceConfigPanelImpl>;
1961
1995
 
1962
1996
  /**
1963
1997
  * Options for simulation behavior
package/dist/index.d.ts CHANGED
@@ -662,7 +662,16 @@ interface DeviceControlPanelProps {
662
662
  /** Embedded mode - uses static positioning for rendering in containers (default: false) */
663
663
  embedded?: boolean;
664
664
  }
665
- declare function DeviceControlPanel({ binding, onClose, position, align, touchOptimized, compact, fontScale, maxHeight, maxWidth, toastDuration, onModeChange, onParameterChange, onParameterActivate, onStart, onStop, startPending, stopPending, onCustomAction, onOpenConfig, showConfigButton, onUndock, undockIcon, modeSelectionStyle, embedded, }: DeviceControlPanelProps): react_jsx_runtime.JSX.Element | null;
665
+ /**
666
+ * Memoized below (kiosk heap-leak fix, same rationale as DeviceConfigPanel):
667
+ * an OPEN control panel must not re-render on host ticks whose props did not
668
+ * change. Unlike the config panel, a control binding legitimately changes when
669
+ * its live values move, so the memo bails only on genuinely idle ticks - the
670
+ * win is proportional to how often values are flat at display precision.
671
+ */
672
+ declare function DeviceControlPanelImpl({ binding, onClose, position, align, touchOptimized, compact, fontScale, maxHeight, maxWidth, toastDuration, onModeChange, onParameterChange, onParameterActivate, onStart, onStop, startPending, stopPending, onCustomAction, onOpenConfig, showConfigButton, onUndock, undockIcon, modeSelectionStyle, embedded, }: DeviceControlPanelProps): react_jsx_runtime.JSX.Element | null;
673
+ /** Memo boundary - see DeviceControlPanelImpl doc comment (kiosk heap-leak fix). */
674
+ declare const DeviceControlPanel: React__default.MemoExoticComponent<typeof DeviceControlPanelImpl>;
666
675
 
667
676
  interface NumpadDialogProps {
668
677
  /** Whether the dialog is open */
@@ -1481,9 +1490,22 @@ interface PIDCanvasControls {
1481
1490
  * - Renders nodes (PR #3) and pipes (PR #4) with static layout
1482
1491
  * - Drag-and-drop: Drag nodes to reposition (PR #8)
1483
1492
  *
1493
+ * Memoized (renderer-OOM fix): a host whose screen re-renders for reasons
1494
+ * unrelated to the canvas (panel state, connection status, ...) should not pay
1495
+ * a full-canvas reconciliation. The memo only pays off when the host passes
1496
+ * identity-stable props (memoized features/telemetry/style, useCallback
1497
+ * handlers); an unstable host degrades gracefully to the old behavior.
1498
+ *
1484
1499
  * Future: Editing tools, symbol library UI
1485
1500
  */
1486
- declare function PIDCanvas({ diagram, className, style, features, onSelectionChange, onNodeClick, onPipeClick, onNodeMove, onDiagramChange, onDelete, onItemsCopied, onPaste, onSymbolDrop, onPipeCreated, onMounted, telemetry, statusColorMap, ...props }: PIDCanvasProps): React__default.ReactElement;
1501
+ declare function PIDCanvasImpl({ diagram, className, style, features, onSelectionChange, onNodeClick, onPipeClick, onNodeMove, onDiagramChange, onDelete, onItemsCopied, onPaste, onSymbolDrop, onPipeCreated, onMounted, telemetry, statusColorMap, ...props }: PIDCanvasProps): React__default.ReactElement;
1502
+ /**
1503
+ * Memo boundary - see PIDCanvasImpl doc comment (renderer-OOM fix). Shallow
1504
+ * compare: `diagram`, `telemetry`, `features`, `style`, `statusColorMap`, and
1505
+ * every callback are compared by identity; hosts that keep them stable skip
1506
+ * the full-canvas re-render entirely when unrelated screen state changes.
1507
+ */
1508
+ declare const PIDCanvas: React__default.MemoExoticComponent<typeof PIDCanvasImpl>;
1487
1509
 
1488
1510
  interface SymbolLibraryProps {
1489
1511
  /** Optional className for styling */
@@ -1957,7 +1979,19 @@ interface DeviceConfigPanelProps {
1957
1979
  /** Embedded mode - uses static positioning for rendering in containers (default: false) */
1958
1980
  embedded?: boolean;
1959
1981
  }
1960
- declare function DeviceConfigPanel({ binding, onClose, onUndock, undockIcon, position, align, touchOptimized, compact, fontScale, maxHeight, maxWidth, toastDuration, onOpenControls, showControlsButton, embedded, }: DeviceConfigPanelProps): react_jsx_runtime.JSX.Element | null;
1982
+ /**
1983
+ * Memoized below (kiosk heap-leak fix): a host whose screen re-renders ~1Hz on
1984
+ * telemetry must not re-render an OPEN config panel every tick - each pass
1985
+ * re-creates the panel's handler closures and re-binds its MUI event handlers,
1986
+ * and on the kiosk those generations were RETAINED (~100 closures/sec,
1987
+ * ~2.1 MB/min heap growth -> renderer OOM at the 512 MB cap, heap-snapshot
1988
+ * fingerprint: Context/scope buckets + onSave/onReset closures). The memo only
1989
+ * pays off when the host passes identity-stable `binding` and handler props;
1990
+ * an unstable host degrades gracefully to the old behavior.
1991
+ */
1992
+ declare function DeviceConfigPanelImpl({ binding, onClose, onUndock, undockIcon, position, align, touchOptimized, compact, fontScale, maxHeight, maxWidth, toastDuration, onOpenControls, showControlsButton, embedded, }: DeviceConfigPanelProps): react_jsx_runtime.JSX.Element | null;
1993
+ /** Memo boundary - see DeviceConfigPanelImpl doc comment (kiosk heap-leak fix). */
1994
+ declare const DeviceConfigPanel: React__default.MemoExoticComponent<typeof DeviceConfigPanelImpl>;
1961
1995
 
1962
1996
  /**
1963
1997
  * Options for simulation behavior
package/dist/index.js CHANGED
@@ -895,7 +895,7 @@ var NodeControlsPanel = ({
895
895
  };
896
896
 
897
897
  // src/components/DeviceControlPanel/DeviceControlPanel.tsx
898
- import { useState as useState5, useEffect as useEffect2 } from "react";
898
+ import React5, { useState as useState5, useEffect as useEffect2 } from "react";
899
899
 
900
900
  // src/theme/statusColors.ts
901
901
  var DEFAULT_STATUS_COLORS = {
@@ -922,7 +922,7 @@ function resolveStatusColor(status, customMap) {
922
922
 
923
923
  // src/components/DeviceControlPanel/DeviceControlPanel.tsx
924
924
  import { Fragment as Fragment2, jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
925
- function DeviceControlPanel({
925
+ function DeviceControlPanelImpl({
926
926
  binding,
927
927
  onClose,
928
928
  position = "bottom",
@@ -1847,6 +1847,7 @@ function DeviceControlPanel({
1847
1847
  ` })
1848
1848
  ] });
1849
1849
  }
1850
+ var DeviceControlPanel = React5.memo(DeviceControlPanelImpl);
1850
1851
 
1851
1852
  // src/components/NumpadDialog/NumpadDialog.tsx
1852
1853
  import { useState as useState6, useEffect as useEffect3, useCallback as useCallback3, useRef as useRef2, useId } from "react";
@@ -3278,7 +3279,10 @@ var ThemeToggle = ({
3278
3279
  };
3279
3280
 
3280
3281
  // src/components/PIDCanvas/PIDCanvas.tsx
3281
- import { useCallback as useCallback8, useState as useState14, useEffect as useEffect12, useRef as useRef6 } from "react";
3282
+ import React13, { useCallback as useCallback8, useState as useState14, useEffect as useEffect12, useRef as useRef6 } from "react";
3283
+
3284
+ // src/components/PIDCanvas/NodeRenderer.tsx
3285
+ import React9 from "react";
3282
3286
 
3283
3287
  // src/diagram/symbols/mockSymbolLibrary.ts
3284
3288
  var mockSymbolLibrary = {
@@ -4500,7 +4504,7 @@ function getAvailableSymbols() {
4500
4504
 
4501
4505
  // src/components/PIDCanvas/NodeRenderer.tsx
4502
4506
  import { jsx as jsx16, jsxs as jsxs12 } from "react/jsx-runtime";
4503
- function NodeRenderer({
4507
+ function NodeRendererImpl({
4504
4508
  nodes: nodes2,
4505
4509
  selectedNodeIds,
4506
4510
  onNodeClick,
@@ -4646,10 +4650,12 @@ function NodeRenderer({
4646
4650
  );
4647
4651
  }) });
4648
4652
  }
4653
+ var NodeRenderer = React9.memo(NodeRendererImpl);
4649
4654
 
4650
4655
  // src/components/PIDCanvas/PipeRenderer.tsx
4656
+ import React10 from "react";
4651
4657
  import { Fragment as Fragment5, jsx as jsx17, jsxs as jsxs13 } from "react/jsx-runtime";
4652
- function PipeRenderer({
4658
+ function PipeRendererImpl({
4653
4659
  pipes,
4654
4660
  selectedPipeIds,
4655
4661
  onPipeClick,
@@ -4826,8 +4832,10 @@ function PipeRenderer({
4826
4832
  );
4827
4833
  }) });
4828
4834
  }
4835
+ var PipeRenderer = React10.memo(PipeRendererImpl);
4829
4836
 
4830
4837
  // src/components/PIDCanvas/DataOverlay.tsx
4838
+ import React11 from "react";
4831
4839
  import { Fragment as Fragment6, jsx as jsx18, jsxs as jsxs14 } from "react/jsx-runtime";
4832
4840
  var PULSE_KEYFRAME_ID = "pid-data-overlay-pulse-keyframes";
4833
4841
  var PULSE_ANIMATION = "pid-data-overlay-pulse 1.5s ease-in-out infinite";
@@ -4853,7 +4861,7 @@ function renderStatusDot(cx, cy, scale, fill) {
4853
4861
  }
4854
4862
  );
4855
4863
  }
4856
- function DataOverlay({
4864
+ function DataOverlayImpl({
4857
4865
  nodeId,
4858
4866
  x,
4859
4867
  y,
@@ -4873,6 +4881,24 @@ function DataOverlay({
4873
4881
  const backgroundColor = display?.backgroundColor;
4874
4882
  const offsetX = display?.offsetX ?? 0;
4875
4883
  const offsetY = display?.offsetY ?? 0;
4884
+ const sparklinePath = React11.useMemo(() => {
4885
+ if (!showSparkline || !history || history.length < 2) return "";
4886
+ const width = 70 * scale;
4887
+ const height = 20 * scale;
4888
+ const padding = 5 * scale;
4889
+ const effectiveWidth = width - padding * 2;
4890
+ const effectiveHeight = height - padding * 2;
4891
+ const hasEuRange = scaling?.euLow !== void 0 && scaling?.euHigh !== void 0 && scaling.euHigh !== scaling.euLow;
4892
+ const min = hasEuRange ? scaling.euLow : Math.min(...history);
4893
+ const max = hasEuRange ? scaling.euHigh : Math.max(...history);
4894
+ const range = max - min || 1;
4895
+ const points = history.map((val, i) => {
4896
+ const xPos = i / (history.length - 1) * effectiveWidth + padding;
4897
+ const yPos = height - padding - (val - min) / range * effectiveHeight;
4898
+ return `${xPos.toFixed(2)},${yPos.toFixed(2)}`;
4899
+ });
4900
+ return `M ${points.join(" L ")}`;
4901
+ }, [showSparkline, history, scaling, scale]);
4876
4902
  if (!showValue) {
4877
4903
  return /* @__PURE__ */ jsx18(Fragment6, {});
4878
4904
  }
@@ -4892,24 +4918,6 @@ function DataOverlay({
4892
4918
  const finalTextColor = textColor || colors.text;
4893
4919
  const formattedValue = formatValue(value.value);
4894
4920
  const displayText = unit ? `${formattedValue} ${unit}` : formattedValue;
4895
- const sparklinePath = (() => {
4896
- if (!showSparkline || !history || history.length < 2) return "";
4897
- const width = 70 * scale;
4898
- const height = 20 * scale;
4899
- const padding = 5 * scale;
4900
- const effectiveWidth = width - padding * 2;
4901
- const effectiveHeight = height - padding * 2;
4902
- const hasEuRange = scaling?.euLow !== void 0 && scaling?.euHigh !== void 0 && scaling.euHigh !== scaling.euLow;
4903
- const min = hasEuRange ? scaling.euLow : Math.min(...history);
4904
- const max = hasEuRange ? scaling.euHigh : Math.max(...history);
4905
- const range = max - min || 1;
4906
- const points = history.map((val, i) => {
4907
- const xPos = i / (history.length - 1) * effectiveWidth + padding;
4908
- const yPos = height - padding - (val - min) / range * effectiveHeight;
4909
- return `${xPos.toFixed(2)},${yPos.toFixed(2)}`;
4910
- });
4911
- return `M ${points.join(" L ")}`;
4912
- })();
4913
4921
  const hasSparkline = showSparkline && sparklinePath;
4914
4922
  const overlayHeight = (label ? hasSparkline ? 55 : 30 : hasSparkline ? 47 : 22) * scale;
4915
4923
  const overlayWidth = 80 * scale;
@@ -4989,6 +4997,7 @@ function DataOverlay({
4989
4997
  ) })
4990
4998
  ] });
4991
4999
  }
5000
+ var DataOverlay = React11.memo(DataOverlayImpl);
4992
5001
 
4993
5002
  // src/components/PIDCanvas/PositionPanel.tsx
4994
5003
  import { useState as useState9, useEffect as useEffect6 } from "react";
@@ -6117,18 +6126,19 @@ function nodeHasPort(node, portId) {
6117
6126
 
6118
6127
  // src/components/PIDCanvas/PIDCanvas.tsx
6119
6128
  import { Fragment as Fragment7, jsx as jsx20, jsxs as jsxs16 } from "react/jsx-runtime";
6120
- function PIDCanvas({
6129
+ var DEFAULT_FEATURES = {
6130
+ pan: true,
6131
+ zoom: true,
6132
+ selection: false,
6133
+ multiSelect: true,
6134
+ telemetry: false,
6135
+ dragNodes: false
6136
+ };
6137
+ function PIDCanvasImpl({
6121
6138
  diagram,
6122
6139
  className = "",
6123
6140
  style,
6124
- features = {
6125
- pan: true,
6126
- zoom: true,
6127
- selection: false,
6128
- multiSelect: true,
6129
- telemetry: false,
6130
- dragNodes: false
6131
- },
6141
+ features = DEFAULT_FEATURES,
6132
6142
  onSelectionChange,
6133
6143
  onNodeClick,
6134
6144
  onPipeClick,
@@ -6589,52 +6599,71 @@ function PIDCanvas({
6589
6599
  if (!selectionEnabled) return;
6590
6600
  onPaste?.();
6591
6601
  }, [selectionEnabled, onPaste]);
6592
- const handleNodeDoubleClick = (nodeId, event) => {
6593
- if (!selectionEnabled) return;
6594
- console.log("Node double-clicked:", nodeId);
6595
- selectNode(nodeId, event.ctrlKey || event.metaKey);
6596
- };
6602
+ const handleNodeDoubleClick = useCallback8(
6603
+ (nodeId, event) => {
6604
+ if (!selectionEnabled) return;
6605
+ console.log("Node double-clicked:", nodeId);
6606
+ selectNode(nodeId, event.ctrlKey || event.metaKey);
6607
+ },
6608
+ [selectionEnabled, selectNode]
6609
+ );
6597
6610
  useKeyboardShortcuts({
6598
6611
  enabled: features.keyboardShortcuts ?? false,
6599
6612
  onDelete: handleDelete,
6600
6613
  onCopy: handleCopy,
6601
6614
  onPaste: handlePaste
6602
6615
  });
6603
- const handleNodeClick = (nodeId, event) => {
6604
- const connectionModeEnabled = features.connectionMode ?? false;
6605
- if (connectionModeEnabled && isConnecting) {
6606
- if (connectionSource && connectionSource !== nodeId) {
6607
- onPipeCreated?.(
6608
- connectionSource,
6609
- nodeId,
6610
- connectionSourcePort || void 0,
6611
- hoveredPort || void 0
6612
- );
6613
- setIsConnecting(false);
6614
- setConnectionSource(null);
6615
- setConnectionSourcePort(null);
6616
- setConnectionCursor(null);
6617
- setHoveredPort(null);
6616
+ const handleNodeClick = useCallback8(
6617
+ (nodeId, event) => {
6618
+ const connectionModeEnabled = features.connectionMode ?? false;
6619
+ if (connectionModeEnabled && isConnecting) {
6620
+ if (connectionSource && connectionSource !== nodeId) {
6621
+ onPipeCreated?.(
6622
+ connectionSource,
6623
+ nodeId,
6624
+ connectionSourcePort || void 0,
6625
+ hoveredPort || void 0
6626
+ );
6627
+ setIsConnecting(false);
6628
+ setConnectionSource(null);
6629
+ setConnectionSourcePort(null);
6630
+ setConnectionCursor(null);
6631
+ setHoveredPort(null);
6632
+ }
6633
+ return;
6618
6634
  }
6619
- return;
6620
- }
6621
- if (connectionModeEnabled && !isConnecting) {
6622
- setIsConnecting(true);
6623
- setConnectionSource(nodeId);
6624
- setConnectionSourcePort(hoveredPort);
6625
- return;
6626
- }
6627
- if (selectionEnabled) {
6628
- selectNode(nodeId, event.ctrlKey || event.metaKey);
6629
- }
6630
- onNodeClick?.(nodeId);
6631
- };
6632
- const handlePipeClick = (pipeId, event) => {
6633
- if (selectionEnabled) {
6634
- selectPipe(pipeId, event.ctrlKey || event.metaKey);
6635
- }
6636
- onPipeClick?.(pipeId);
6637
- };
6635
+ if (connectionModeEnabled && !isConnecting) {
6636
+ setIsConnecting(true);
6637
+ setConnectionSource(nodeId);
6638
+ setConnectionSourcePort(hoveredPort);
6639
+ return;
6640
+ }
6641
+ if (selectionEnabled) {
6642
+ selectNode(nodeId, event.ctrlKey || event.metaKey);
6643
+ }
6644
+ onNodeClick?.(nodeId);
6645
+ },
6646
+ [
6647
+ features.connectionMode,
6648
+ isConnecting,
6649
+ connectionSource,
6650
+ connectionSourcePort,
6651
+ hoveredPort,
6652
+ onPipeCreated,
6653
+ selectionEnabled,
6654
+ selectNode,
6655
+ onNodeClick
6656
+ ]
6657
+ );
6658
+ const handlePipeClick = useCallback8(
6659
+ (pipeId, event) => {
6660
+ if (selectionEnabled) {
6661
+ selectPipe(pipeId, event.ctrlKey || event.metaKey);
6662
+ }
6663
+ onPipeClick?.(pipeId);
6664
+ },
6665
+ [selectionEnabled, selectPipe, onPipeClick]
6666
+ );
6638
6667
  const handleCanvasClick = (e) => {
6639
6668
  const target = e.target;
6640
6669
  if (target.closest(".pipe-route-point")) {
@@ -6982,6 +7011,7 @@ function PIDCanvas({
6982
7011
  }
6983
7012
  );
6984
7013
  }
7014
+ var PIDCanvas = React13.memo(PIDCanvasImpl);
6985
7015
 
6986
7016
  // src/components/SymbolLibrary/SymbolLibrary.tsx
6987
7017
  import { useState as useState15 } from "react";
@@ -8561,9 +8591,9 @@ function useDeviceConfigs(initialConfigs) {
8561
8591
  }
8562
8592
 
8563
8593
  // src/components/DeviceConfigPanel/DeviceConfigPanel.tsx
8564
- import { useState as useState18, useEffect as useEffect13, useMemo, useRef as useRef8 } from "react";
8594
+ import React15, { useState as useState18, useEffect as useEffect13, useMemo, useRef as useRef8 } from "react";
8565
8595
  import { Fragment as Fragment9, jsx as jsx23, jsxs as jsxs19 } from "react/jsx-runtime";
8566
- function DeviceConfigPanel({
8596
+ function DeviceConfigPanelImpl({
8567
8597
  binding,
8568
8598
  onClose,
8569
8599
  onUndock,
@@ -9265,6 +9295,7 @@ function DeviceConfigPanel({
9265
9295
  )
9266
9296
  ] });
9267
9297
  }
9298
+ var DeviceConfigPanel = React15.memo(DeviceConfigPanelImpl);
9268
9299
 
9269
9300
  // src/diagram/hooks/useSimulation.ts
9270
9301
  import { useEffect as useEffect14 } from "react";