+ );
+ };
+
+ return ;
+}
+
+// In your mode or extension, add this to the customizations
+// This example shows how to add it in the onModeEnter lifecycle hook
+function onModeEnter({ servicesManager }) {
+ const { customizationService } = servicesManager.services;
+
+ // Add the mode switch icon to the top-left corner of the viewport
+ customizationService.setCustomizations({
+ 'viewportActionMenu.topLeft': {
+ // Use $push to add to existing items or $set to replace all items
+ $push: [
+ {
+ id: 'modeSwitch',
+ enabled: true,
+ component: getModeSwitchMenu,
+ },
+ ],
+ },
+ });
+}
+```
+
+## Key Concepts
+
+1. **Location-based customization**: The viewport is divided into four corners identified by:
+ - `viewportActionMenu.topLeft`
+ - `viewportActionMenu.topRight`
+ - `viewportActionMenu.bottomLeft`
+ - `viewportActionMenu.bottomRight`
+
+2. **Component structure**:
+ - `id` - A unique identifier for your component
+ - `enabled` - Boolean to control if the component should be displayed
+ - `component` - A function that returns a React component to render
+
+3. **Component positioning**: The component's position within a corner is determined by its order in the array. Components are rendered in the order they appear.
+
+4. **Dropdown positioning**: Use the `viewportActionCornersService.getAlignAndSide()` method to get the correct alignment for your dropdown menu based on its location.
+
+## Adding Multiple Components
+
+If you want to add multiple components to the same corner or different corners, you can do it in a single customization:
+
+```js
+customizationService.setCustomizations({
+ 'viewportActionMenu.topLeft': {
+ $push: [
+ {
+ id: 'modeSwitch',
+ enabled: true,
+ component: getModeSwitchMenu,
+ },
+ ],
+ },
+ 'viewportActionMenu.topRight': {
+ $push: [
+ {
+ id: 'anotherComponent',
+ enabled: true,
+ component: getAnotherComponent,
+ },
+ ],
+ },
+});
+```
+
+## Replacing Existing Components
+
+If you want to replace all components in a corner instead of adding to them, use `$set` instead of `$push`:
+
+```js
+customizationService.setCustomizations({
+ 'viewportActionMenu.topLeft': {
+ $set: [
+ {
+ id: 'modeSwitch',
+ enabled: true,
+ component: getModeSwitchMenu,
+ },
+ // This will be the only components in the top-left corner
+ ],
+ },
+});
+```
+
+Remember that this customization will affect all viewports in the active mode. If you need different behavior for different viewports, you should check the `viewportId` parameter in your component's logic.
diff --git a/platform/docs/docs/faq/technical.md b/platform/docs/docs/faq/technical.md
index 1b65ec67d..785138076 100644
--- a/platform/docs/docs/faq/technical.md
+++ b/platform/docs/docs/faq/technical.md
@@ -6,6 +6,7 @@ summary: Technical explanations and solutions for OHIF Viewer implementation cha
# Technical FAQ
+* [How to add a custom icon to the viewport corners](./add-viewport-icon.md)
## Viewer opens but does not show any thumbnails
diff --git a/platform/docs/docs/migration-guide/3p10-to-3p11/index.md b/platform/docs/docs/migration-guide/3p10-to-3p11/index.md
new file mode 100644
index 000000000..bee159b84
--- /dev/null
+++ b/platform/docs/docs/migration-guide/3p10-to-3p11/index.md
@@ -0,0 +1,22 @@
+---
+sidebar_position: 1
+sidebar_label: 3.10 -> 3.11 beta
+---
+
+# Migration Guide
+
+This guide provides information about migrating from OHIF version 3.10 to version 3.11.
+
+## General
+
+`viewportActionMenu.segmentationOverlay` is renamed to `viewportActionMenu.dataOverlay`
+as it handles now both segmentation and data overlay.
+
+## Viewport Action Menu Customization
+
+The structure for defining viewport action menu customizations has changed. See the [Viewport Action Menu](./viewport-action-menu.md) migration guide for details.
+
+
+## updateStoredPositionPresentation
+
+now uses displaySetInstanceUIDs instead of displaySetInstanceUID.
diff --git a/platform/docs/docs/migration-guide/3p10-to-3p11/viewport-action-menu.md b/platform/docs/docs/migration-guide/3p10-to-3p11/viewport-action-menu.md
new file mode 100644
index 000000000..2b7f36d98
--- /dev/null
+++ b/platform/docs/docs/migration-guide/3p10-to-3p11/viewport-action-menu.md
@@ -0,0 +1,102 @@
+---
+sidebar_position: 2
+sidebar_label: Viewport Action Menu
+summary: Migration guide for OHIF 3.11's viewport action menu customization changes, including the transition from individual item configurations to location-based arrays and the removal of index priorities.
+---
+
+# Viewport Action Menu Customization
+
+In OHIF 3.11, we've redesigned how viewport action menu customizations are defined to make them more intuitive and organized by location.
+
+## Changes
+
+Previously, viewport action menu customizations were defined with individual item configurations that specified location and priority:
+
+```ts
+export default {
+ 'viewportActionMenu.orientationMenu': {
+ enabled: true,
+ location: viewportActionCornersService.LOCATIONS.topLeft,
+ indexPriority: 1,
+ },
+ 'viewportActionMenu.dataOverlay': {
+ enabled: true,
+ location: viewportActionCornersService.LOCATIONS.topLeft,
+ indexPriority: 2,
+ },
+ // ...
+};
+```
+
+Now, viewport action menu customizations are organized by location, with each location having its own customization ID:
+
+```ts
+export default {
+ 'viewportActionMenu.topLeft': [
+ {
+ id: 'orientationMenu',
+ enabled: true,
+ },
+ {
+ id: 'dataOverlay',
+ enabled: true,
+ },
+ {
+ id: 'windowLevelActionMenu',
+ enabled: true,
+ },
+ ],
+ 'viewportActionMenu.topRight': [],
+ 'viewportActionMenu.bottomLeft': [],
+ 'viewportActionMenu.bottomRight': [],
+};
+```
+
+## Migration Steps
+
+1. Reorganize your viewport action menu customizations by using location-based customization IDs (`viewportActionMenu.topLeft`, `viewportActionMenu.topRight`, etc.).
+2. For each component, move it into the appropriate location array.
+3. Replace the component key with an `id` property that doesn't include the prefix (just use `orientationMenu` instead of `viewportActionMenu.orientationMenu`).
+4. Remove the `location` property (since it's now implied by the customization ID).
+5. Remove the `indexPriority` property (order in the array now determines display order).
+6. For each component, provide a `component` function that returns the component instance.
+
+## Component Rendering
+
+Component rendering logic is now included directly in the item configuration via a `component` function:
+
+```ts
+const createOrientationMenu = ({ viewportId, element, location }) => {
+ return getViewportOrientationMenu({
+ viewportId,
+ element,
+ location,
+ });
+};
+
+const createDataOverlay = ({ viewportId, element, displaySets, location }) => {
+ return getViewportDataOverlaySettingsMenu({
+ viewportId,
+ element,
+ displaySets,
+ location,
+ });
+};
+
+export default {
+ 'viewportActionMenu.topLeft': [
+ {
+ id: 'orientationMenu',
+ enabled: true,
+ component: createOrientationMenu,
+ },
+ {
+ id: 'dataOverlay',
+ enabled: true,
+ component: createDataOverlay,
+ },
+ // other components...
+ ],
+ // other locations...
+};
+```
diff --git a/platform/docs/docs/migration-guide/3p9-to-3p10/index.md b/platform/docs/docs/migration-guide/3p9-to-3p10/index.md
index cf77d26f1..e2da5ae32 100644
--- a/platform/docs/docs/migration-guide/3p9-to-3p10/index.md
+++ b/platform/docs/docs/migration-guide/3p9-to-3p10/index.md
@@ -1,6 +1,6 @@
---
sidebar_position: 1
-sidebar_label: 3.9 -> 3.10 beta
+sidebar_label: 3.9 -> 3.10
title: Migration Guide from 3.9 to 3.10 beta
summary: Migration guide for upgrading from OHIF 3.9 to 3.10 beta, covering general changes, customization service improvements, UI component upgrades, command handling, hotkey updates, routing changes, and testing strategies.
---
diff --git a/platform/docs/docs/platform/services/customization-service/sampleCustomizations.tsx b/platform/docs/docs/platform/services/customization-service/sampleCustomizations.tsx
index 6ab5b84ee..4b70c0252 100644
--- a/platform/docs/docs/platform/services/customization-service/sampleCustomizations.tsx
+++ b/platform/docs/docs/platform/services/customization-service/sampleCustomizations.tsx
@@ -937,8 +937,8 @@ window.config = {
`,
},
{
- id: 'viewportActionMenu.segmentationOverlay',
- description: 'Configures the display and location of the segmentation overlay in the viewport.',
+ id: 'viewportActionMenu.dataOverlay',
+ description: 'Configures the display and location of the data overlay in the viewport.',
image: segmentationOverlay,
default: null,
configuration: `
@@ -946,7 +946,7 @@ window.config = {
// rest of window config
customizationService: [
{
- 'viewportActionMenu.segmentationOverlay': {
+ 'viewportActionMenu.dataOverlay': {
$merge: {
enabled: true,
location: 1, // Set the location of the overlay in the viewport.
diff --git a/platform/ui-next/src/components/DataRow/DataRow.tsx b/platform/ui-next/src/components/DataRow/DataRow.tsx
index 31e5ef47d..e0b654063 100644
--- a/platform/ui-next/src/components/DataRow/DataRow.tsx
+++ b/platform/ui-next/src/components/DataRow/DataRow.tsx
@@ -1,4 +1,4 @@
-import React, { useState, useEffect, useRef } from 'react';
+import React, { useState, useRef } from 'react';
import { Button } from '../../components/Button/Button';
import {
DropdownMenu,
@@ -57,7 +57,7 @@ import { cn } from '../../lib/utils';
* @property {() => void} onColor - Callback when color change is requested
*/
interface DataRowProps {
- number: number;
+ number: number | null;
disableEditing: boolean;
description: string;
details?: { primary: string[]; secondary: string[] };
@@ -208,15 +208,18 @@ export const DataRow: React.FC = ({
{/* Number Box */}
-
- {number}
-
+ {number !== null && (
+
+ {number}
+
+ )}
- {/* Color Circle (Optional) */}
+ {/* add some space if there is not segment index */}
+ {number === null && }
{colorHex && (
void;
showNumberInputs?: boolean;
+ lockMode?: boolean;
}
const DoubleSlider = React.forwardRef(
@@ -24,10 +25,19 @@ const DoubleSlider = React.forwardRef(
step = 1,
defaultValue = [min, max],
showNumberInputs = false,
+ lockMode = false,
},
ref
) => {
const [value, setValue] = React.useState<[number, number]>(defaultValue);
+ const trackRef = React.useRef(null);
+ const dragStateRef = React.useRef<{
+ startX: number;
+ startValue: [number, number];
+ rangeWidth: number;
+ trackLeft: number;
+ trackWidth: number;
+ } | null>(null);
const prevDefaultValueRef = React.useRef<[number, number] | null>(null);
@@ -50,8 +60,172 @@ const DoubleSlider = React.forwardRef(
return Math.round(num * inverse) / inverse;
};
+ const handleRangePointerDown = (event: React.PointerEvent) => {
+ if (!lockMode) {
+ return;
+ }
+ event.preventDefault();
+ const rect = trackRef.current!.getBoundingClientRect();
+ dragStateRef.current = {
+ startX: event.clientX,
+ startValue: [...value] as [number, number],
+ rangeWidth: value[1] - value[0],
+ trackLeft: rect.left,
+ trackWidth: rect.width,
+ };
+ window.addEventListener('pointermove', handleRangePointerMove);
+ window.addEventListener('pointerup', handleRangePointerUp, { once: true });
+ };
+
+ const handleRangePointerMove = (event: PointerEvent) => {
+ const state = dragStateRef.current;
+ if (!state) {
+ return;
+ }
+ const { startX, startValue, rangeWidth, trackWidth } = state;
+ const dxPx = event.clientX - startX;
+ const pct = dxPx / trackWidth;
+ const delta = pct * (max - min);
+
+ let newLeft = startValue[0] + delta;
+ let newRight = newLeft + rangeWidth;
+
+ // clamp to bounds
+ if (newLeft < min) {
+ newLeft = min;
+ newRight = min + rangeWidth;
+ } else if (newRight > max) {
+ newRight = max;
+ newLeft = max - rangeWidth;
+ }
+
+ const clampedLeft = roundToStep(newLeft);
+ const clampedRight = roundToStep(newRight);
+ setValue([clampedLeft, clampedRight]);
+ onValueChange?.([clampedLeft, clampedRight]);
+ };
+
+ const handleRangePointerUp = () => {
+ dragStateRef.current = null;
+ window.removeEventListener('pointermove', handleRangePointerMove);
+ };
+
const handleSliderChange = React.useCallback(
(newValue: number[]) => {
+ // Default behavior (no lock mode)
+ if (!lockMode) {
+ const clampedValue: [number, number] = [
+ roundToStep(Math.max(min, Math.min(newValue[0], max))),
+ roundToStep(Math.min(max, Math.max(newValue[1], min))),
+ ];
+ setValue(clampedValue);
+ onValueChange?.(clampedValue);
+ return;
+ }
+
+ // Lock mode behavior
+ // First, determine if this is a symmetric expansion/contraction or a range shift
+ // by checking which thumb(s) moved
+ const isLeftThumbMoved = newValue[0] !== value[0];
+ const isRightThumbMoved = newValue[1] !== value[1];
+
+ // Case 1: Left thumb moved, right thumb needs to move symmetrically (opposite)
+ if (isLeftThumbMoved && !isRightThumbMoved) {
+ const delta = newValue[0] - value[0];
+ const centerPoint = (value[0] + value[1]) / 2;
+
+ // Symmetric movement: move right thumb in opposite direction
+ const newRight = value[1] - delta;
+
+ // Ensure values stay within bounds
+ const clampedLeft = roundToStep(Math.max(min, Math.min(newValue[0], max)));
+ const clampedRight = roundToStep(Math.min(max, Math.max(newRight, min)));
+
+ // Special case: If right would go out of bounds, adjust left too to maintain center
+ if (newRight !== clampedRight) {
+ // Calculate new delta given the clamped right value
+ const adjustedDelta = value[1] - clampedRight;
+ // Apply same delta to left but in opposite direction
+ const adjustedLeft = value[0] + adjustedDelta;
+ const finalClampedLeft = roundToStep(Math.max(min, Math.min(adjustedLeft, max)));
+
+ const finalValues: [number, number] = [finalClampedLeft, clampedRight];
+ setValue(finalValues);
+ onValueChange?.(finalValues);
+ return;
+ }
+
+ const finalValues: [number, number] = [clampedLeft, clampedRight];
+ setValue(finalValues);
+ onValueChange?.(finalValues);
+ return;
+ }
+
+ // Case 2: Right thumb moved, left thumb needs to move symmetrically (opposite)
+ if (!isLeftThumbMoved && isRightThumbMoved) {
+ const delta = newValue[1] - value[1];
+ const centerPoint = (value[0] + value[1]) / 2;
+
+ // Symmetric movement: move left thumb in opposite direction
+ const newLeft = value[0] - delta;
+
+ // Ensure values stay within bounds
+ const clampedRight = roundToStep(Math.min(max, Math.max(newValue[1], min)));
+ const clampedLeft = roundToStep(Math.max(min, Math.min(newLeft, max)));
+
+ // Special case: If left would go out of bounds, adjust right too to maintain center
+ if (newLeft !== clampedLeft) {
+ // Calculate new delta given the clamped left value
+ const adjustedDelta = value[0] - clampedLeft;
+ // Apply same delta to right but in opposite direction
+ const adjustedRight = value[1] + adjustedDelta;
+ const finalClampedRight = roundToStep(Math.min(max, Math.max(adjustedRight, min)));
+
+ const finalValues: [number, number] = [clampedLeft, finalClampedRight];
+ setValue(finalValues);
+ onValueChange?.(finalValues);
+ return;
+ }
+
+ const finalValues: [number, number] = [clampedLeft, clampedRight];
+ setValue(finalValues);
+ onValueChange?.(finalValues);
+ return;
+ }
+
+ // Case 3: Both thumbs moved (range shift) - maintain the same distance between thumbs
+ if (isLeftThumbMoved && isRightThumbMoved) {
+ const rangeWidth = value[1] - value[0];
+
+ // Calculate how much the left thumb moved
+ const leftDelta = newValue[0] - value[0];
+
+ // New values maintaining the same range width
+ let newLeft = newValue[0];
+ let newRight = newLeft + rangeWidth;
+
+ // Handle bounds checking
+ if (newRight > max) {
+ newRight = max;
+ newLeft = newRight - rangeWidth;
+ }
+
+ if (newLeft < min) {
+ newLeft = min;
+ newRight = newLeft + rangeWidth;
+ }
+
+ const clampedValues: [number, number] = [
+ roundToStep(Math.max(min, Math.min(newLeft, max))),
+ roundToStep(Math.min(max, Math.max(newRight, min))),
+ ];
+
+ setValue(clampedValues);
+ onValueChange?.(clampedValues);
+ return;
+ }
+
+ // Fallback to default behavior
const clampedValue: [number, number] = [
roundToStep(Math.max(min, Math.min(newValue[0], max))),
roundToStep(Math.min(max, Math.max(newValue[1], min))),
@@ -59,25 +233,86 @@ const DoubleSlider = React.forwardRef(
setValue(clampedValue);
onValueChange?.(clampedValue);
},
- [min, max, onValueChange, step]
+ [min, max, onValueChange, step, value, lockMode]
);
const handleInputChange = React.useCallback(
(index: 0 | 1, inputValue: string) => {
const newValue = parseFloat(inputValue);
if (!isNaN(newValue)) {
- const clampedValue: [number, number] = [...value];
- clampedValue[index] = roundToStep(Math.min(Math.max(newValue, min), max));
- if (index === 0 && clampedValue[0] > clampedValue[1]) {
- clampedValue[1] = clampedValue[0];
- } else if (index === 1 && clampedValue[1] < clampedValue[0]) {
- clampedValue[0] = clampedValue[1];
+ // Default behavior (no lock mode)
+ if (!lockMode) {
+ const clampedValue: [number, number] = [...value];
+ clampedValue[index] = roundToStep(Math.min(Math.max(newValue, min), max));
+ if (index === 0 && clampedValue[0] > clampedValue[1]) {
+ clampedValue[1] = clampedValue[0];
+ } else if (index === 1 && clampedValue[1] < clampedValue[0]) {
+ clampedValue[0] = clampedValue[1];
+ }
+ setValue(clampedValue);
+ onValueChange?.(clampedValue);
+ return;
}
+
+ // Lock mode behavior
+ const centerPoint = (value[0] + value[1]) / 2;
+ const rangeWidth = value[1] - value[0];
+ const clampedValue: [number, number] = [...value];
+
+ // Calculate new value with constraints
+ const boundedNewValue = roundToStep(Math.min(Math.max(newValue, min), max));
+
+ if (index === 0) {
+ // Left thumb changed
+ const delta = boundedNewValue - value[0];
+
+ // Symmetric change: move right thumb in opposite direction
+ const newRight = value[1] - delta;
+ const boundedNewRight = roundToStep(Math.min(Math.max(newRight, min), max));
+
+ // If right would go out of bounds, adjust left too
+ if (newRight !== boundedNewRight) {
+ // Adjust to maintain the same width but respect bounds
+ if (boundedNewRight === max) {
+ clampedValue[0] = roundToStep(Math.max(max - rangeWidth, min));
+ clampedValue[1] = max;
+ } else if (boundedNewRight === min) {
+ clampedValue[0] = min;
+ clampedValue[1] = roundToStep(Math.min(min + rangeWidth, max));
+ }
+ } else {
+ clampedValue[0] = boundedNewValue;
+ clampedValue[1] = boundedNewRight;
+ }
+ } else {
+ // Right thumb changed
+ const delta = boundedNewValue - value[1];
+
+ // Symmetric change: move left thumb in opposite direction
+ const newLeft = value[0] - delta;
+ const boundedNewLeft = roundToStep(Math.min(Math.max(newLeft, min), max));
+
+ // If left would go out of bounds, adjust right too
+ if (newLeft !== boundedNewLeft) {
+ // Adjust to maintain the same width but respect bounds
+ if (boundedNewLeft === min) {
+ clampedValue[0] = min;
+ clampedValue[1] = roundToStep(Math.min(min + rangeWidth, max));
+ } else if (boundedNewLeft === max) {
+ clampedValue[0] = roundToStep(Math.max(max - rangeWidth, min));
+ clampedValue[1] = max;
+ }
+ } else {
+ clampedValue[0] = boundedNewLeft;
+ clampedValue[1] = boundedNewValue;
+ }
+ }
+
setValue(clampedValue);
onValueChange?.(clampedValue);
}
},
- [value, min, max, onValueChange, step]
+ [value, min, max, onValueChange, step, lockMode]
);
const formatValue = (val: number) => {
@@ -109,8 +344,14 @@ const DoubleSlider = React.forwardRef(
value={value}
onValueChange={handleSliderChange}
>
-
-
+
+
diff --git a/platform/ui-next/src/components/Icons/Icons.tsx b/platform/ui-next/src/components/Icons/Icons.tsx
index 4b06a5b96..953161288 100644
--- a/platform/ui-next/src/components/Icons/Icons.tsx
+++ b/platform/ui-next/src/components/Icons/Icons.tsx
@@ -75,6 +75,10 @@ import IconTransferring from './Sources/IconTransferring';
import Alert from './Sources/Alert';
import AlertOutline from './Sources/AlertOutline';
import Clipboard from './Sources/Clipboard';
+import OrientationSwitch from './Sources/OrientationSwitch';
+import LayerBackground from './Sources/LayerBackground';
+import LayerForeground from './Sources/LayerForeground';
+import LayerSegmentation from './Sources/LayerSegmentation';
import {
Tool3DRotate,
ToolAngle,
@@ -378,6 +382,10 @@ export const Icons = {
/>
),
// Icons
+ LayerBackground,
+ LayerForeground,
+ LayerSegmentation,
+ OrientationSwitch,
Clipboard,
ActionNewDialog,
GroupLayers,
diff --git a/platform/ui-next/src/components/Icons/Sources/LayerBackground.tsx b/platform/ui-next/src/components/Icons/Sources/LayerBackground.tsx
new file mode 100644
index 000000000..d25f0387d
--- /dev/null
+++ b/platform/ui-next/src/components/Icons/Sources/LayerBackground.tsx
@@ -0,0 +1,28 @@
+import React from 'react';
+import type { IconProps } from '../types';
+
+export const LayerBackground = (props: IconProps) => (
+
+);
+
+export default LayerBackground;
diff --git a/platform/ui-next/src/components/Icons/Sources/LayerForeground.tsx b/platform/ui-next/src/components/Icons/Sources/LayerForeground.tsx
new file mode 100644
index 000000000..fa9ed849b
--- /dev/null
+++ b/platform/ui-next/src/components/Icons/Sources/LayerForeground.tsx
@@ -0,0 +1,34 @@
+import React from 'react';
+import type { IconProps } from '../types';
+
+export const LayerForeground = (props: IconProps) => (
+
+);
+
+export default LayerForeground;
diff --git a/platform/ui-next/src/components/Icons/Sources/LayerSegmentation.tsx b/platform/ui-next/src/components/Icons/Sources/LayerSegmentation.tsx
new file mode 100644
index 000000000..7395da9e4
--- /dev/null
+++ b/platform/ui-next/src/components/Icons/Sources/LayerSegmentation.tsx
@@ -0,0 +1,20 @@
+import React from 'react';
+import type { IconProps } from '../types';
+
+export const LayerSegmentation = (props: IconProps) => (
+
+);
+
+export default LayerSegmentation;
diff --git a/platform/ui-next/src/components/Icons/Sources/OrientationSwitch.tsx b/platform/ui-next/src/components/Icons/Sources/OrientationSwitch.tsx
new file mode 100644
index 000000000..440976e3f
--- /dev/null
+++ b/platform/ui-next/src/components/Icons/Sources/OrientationSwitch.tsx
@@ -0,0 +1,41 @@
+import React from 'react';
+import type { IconProps } from '../types';
+
+export const OrientationSwitch = (props: IconProps) => (
+
+);
+
+export default OrientationSwitch;
diff --git a/platform/ui-next/src/components/Numeric/Numeric.tsx b/platform/ui-next/src/components/Numeric/Numeric.tsx
index 40badb61f..eca111654 100644
--- a/platform/ui-next/src/components/Numeric/Numeric.tsx
+++ b/platform/ui-next/src/components/Numeric/Numeric.tsx
@@ -29,6 +29,7 @@ interface NumericMetaContextValue {
min: number;
max: number;
step: number;
+ lockMode?: boolean;
}
const NumericMetaContext = createContext(null);
@@ -47,6 +48,7 @@ interface NumericMetaContainerProps {
max?: number;
step?: number;
className?: string;
+ lockMode?: boolean; // Add lock mode prop for doubleRange
}
function NumericMetaContainer({
@@ -60,6 +62,7 @@ function NumericMetaContainer({
max = 100,
step = 1,
className,
+ lockMode = false,
children,
}: PropsWithChildren) {
// Calculate default values based on min and max
@@ -115,6 +118,7 @@ function NumericMetaContainer({
min,
max,
step,
+ lockMode,
}}
>