diff --git a/shesha-core/src/Shesha.Application/ConfigMigrations/package20260304_1119.shaconfig b/shesha-core/src/Shesha.Application/ConfigMigrations/package20260304_1119.shaconfig new file mode 100644 index 0000000000..5387c65f54 Binary files /dev/null and b/shesha-core/src/Shesha.Application/ConfigMigrations/package20260304_1119.shaconfig differ diff --git a/shesha-core/src/Shesha.Application/ConfigMigrations/package20260306_1033.shaconfig b/shesha-core/src/Shesha.Application/ConfigMigrations/package20260306_1033.shaconfig new file mode 100644 index 0000000000..a9c8d4e5f5 Binary files /dev/null and b/shesha-core/src/Shesha.Application/ConfigMigrations/package20260306_1033.shaconfig differ diff --git a/shesha-core/src/Shesha.Application/ConfigMigrations/package20260306_1109.shaconfig b/shesha-core/src/Shesha.Application/ConfigMigrations/package20260306_1109.shaconfig new file mode 100644 index 0000000000..67cf4d58e9 Binary files /dev/null and b/shesha-core/src/Shesha.Application/ConfigMigrations/package20260306_1109.shaconfig differ diff --git a/shesha-core/src/Shesha.Application/ConfigMigrations/package20260306_1135.shaconfig b/shesha-core/src/Shesha.Application/ConfigMigrations/package20260306_1135.shaconfig new file mode 100644 index 0000000000..30ff0dae92 Binary files /dev/null and b/shesha-core/src/Shesha.Application/ConfigMigrations/package20260306_1135.shaconfig differ diff --git a/shesha-core/src/Shesha.Application/ConfigMigrations/package20260309_1350.shaconfig b/shesha-core/src/Shesha.Application/ConfigMigrations/package20260309_1350.shaconfig new file mode 100644 index 0000000000..35721dd721 Binary files /dev/null and b/shesha-core/src/Shesha.Application/ConfigMigrations/package20260309_1350.shaconfig differ diff --git a/shesha-core/src/Shesha.Application/ConfigMigrations/package20260309_1427.shaconfig b/shesha-core/src/Shesha.Application/ConfigMigrations/package20260309_1427.shaconfig new file mode 100644 index 0000000000..92b3709c38 Binary files /dev/null and b/shesha-core/src/Shesha.Application/ConfigMigrations/package20260309_1427.shaconfig differ diff --git a/shesha-core/src/Shesha.Application/ConfigMigrations/package20260309_1622.shaconfig b/shesha-core/src/Shesha.Application/ConfigMigrations/package20260309_1622.shaconfig new file mode 100644 index 0000000000..39354c3ebb Binary files /dev/null and b/shesha-core/src/Shesha.Application/ConfigMigrations/package20260309_1622.shaconfig differ diff --git a/shesha-core/src/Shesha.Application/ConfigMigrations/package20260310_1015.shaconfig b/shesha-core/src/Shesha.Application/ConfigMigrations/package20260310_1015.shaconfig new file mode 100644 index 0000000000..3ce61ec558 Binary files /dev/null and b/shesha-core/src/Shesha.Application/ConfigMigrations/package20260310_1015.shaconfig differ diff --git a/shesha-core/src/Shesha.Application/Shesha.Application.csproj b/shesha-core/src/Shesha.Application/Shesha.Application.csproj index 8bdf8826b2..5a36db44f3 100644 --- a/shesha-core/src/Shesha.Application/Shesha.Application.csproj +++ b/shesha-core/src/Shesha.Application/Shesha.Application.csproj @@ -113,6 +113,14 @@ + + + + + + + + @@ -123,6 +131,14 @@ + + + + + + + + diff --git a/shesha-reactjs/package-lock.json b/shesha-reactjs/package-lock.json index 0924335b0e..6a7ce70205 100644 --- a/shesha-reactjs/package-lock.json +++ b/shesha-reactjs/package-lock.json @@ -47,7 +47,7 @@ "rc-picker": "^4.11.3", "react-ace": "^10.1.0", "react-beautiful-dnd": "^13.1.0", - "react-big-calendar": "^1.15.0", + "react-big-calendar": "^1.19.4", "react-chartjs-2": "^5.3.0", "react-error-boundary": "^3.1.4", "react-fast-marquee": "^1.6.5", diff --git a/shesha-reactjs/package.json b/shesha-reactjs/package.json index 61247efcbe..4a8dda7d49 100644 --- a/shesha-reactjs/package.json +++ b/shesha-reactjs/package.json @@ -186,7 +186,7 @@ "rc-picker": "^4.11.3", "react-ace": "^10.1.0", "react-beautiful-dnd": "^13.1.0", - "react-big-calendar": "^1.15.0", + "react-big-calendar": "^1.19.4", "react-chartjs-2": "^5.3.0", "react-error-boundary": "^3.1.4", "react-fast-marquee": "^1.6.5", diff --git a/shesha-reactjs/src/components/calendar/index.tsx b/shesha-reactjs/src/components/calendar/index.tsx index fe0b829be0..6d3c3faff1 100644 --- a/shesha-reactjs/src/components/calendar/index.tsx +++ b/shesha-reactjs/src/components/calendar/index.tsx @@ -343,7 +343,7 @@ export const CalendarControl: FC = (props) => { onDoubleClickEvent={handleCustomDoubleClick} onSelectSlot={handleSlotClick} eventPropGetter={(event: any) => ({ - style: { backgroundColor: event.color || primaryColor, height: '100%' }, + style: { backgroundColor: event.color || primaryColor }, })} components={{ event: EventComponent, diff --git a/shesha-reactjs/src/components/configurableForm/styles/styles.ts b/shesha-reactjs/src/components/configurableForm/styles/styles.ts index 1f05f611cc..347fe24bb6 100644 --- a/shesha-reactjs/src/components/configurableForm/styles/styles.ts +++ b/shesha-reactjs/src/components/configurableForm/styles/styles.ts @@ -33,6 +33,7 @@ export const ShaFormStyles = createGlobalStyle` .${formClassNames.shaForm} { .${formClassNames.shaComponentsContainer} { min-height: 32px; + height: 100%; &.horizontal { .${formClassNames.shaComponentsContainerInner} { display: flex; diff --git a/shesha-reactjs/src/components/dataList/index.tsx b/shesha-reactjs/src/components/dataList/index.tsx index 5ea297391d..7f84abead7 100644 --- a/shesha-reactjs/src/components/dataList/index.tsx +++ b/shesha-reactjs/src/components/dataList/index.tsx @@ -764,6 +764,7 @@ export const DataList: FC> = ({ default: return { ...containerStyles, + width: '100%', display: 'grid', gridTemplateColumns: '1fr', alignItems: 'stretch', diff --git a/shesha-reactjs/src/components/dataList/styles/styles.ts b/shesha-reactjs/src/components/dataList/styles/styles.ts index 93f8bf6194..d761715529 100644 --- a/shesha-reactjs/src/components/dataList/styles/styles.ts +++ b/shesha-reactjs/src/components/dataList/styles/styles.ts @@ -21,10 +21,6 @@ export const useStyles = createStyles(({ css, cx, token, prefixCls }) => { margin: unset !important; } - .ant-collapse>.ant-collapse-item >.ant-collapse-header { - margin: 4px !important; - } - .ant-divider-horizontal{ min-width: unset !important; } @@ -211,4 +207,4 @@ export const useStyles = createStyles(({ css, cx, token, prefixCls }) => { shaDatalistCard, shaDatalistHorizontal, }; -}); +}); diff --git a/shesha-reactjs/src/components/dropdown/style.ts b/shesha-reactjs/src/components/dropdown/style.ts index ae90ce4228..fee6dad1b0 100644 --- a/shesha-reactjs/src/components/dropdown/style.ts +++ b/shesha-reactjs/src/components/dropdown/style.ts @@ -1,7 +1,7 @@ import { createStyles } from '@/styles'; import { CSSProperties } from 'react'; -export const useStyles = createStyles(({ css, cx, token }, { style }: { style: CSSProperties }) => { +export const useStyles = createStyles(({ css, cx, token }, { style }: { style: CSSProperties }) => { const dropdown = cx("sha-dropdown", css` --ant-color-text: ${style.color} !important; --ant-font-weight-strong: ${style.fontWeight} !important; @@ -71,4 +71,4 @@ export const useStyles = createStyles(({ css, cx, token }, { style }: { style: C return { dropdown, }; -}); +}); diff --git a/shesha-reactjs/src/components/entityPicker/index.tsx b/shesha-reactjs/src/components/entityPicker/index.tsx index e8199a0bcc..8fcd6d5e49 100644 --- a/shesha-reactjs/src/components/entityPicker/index.tsx +++ b/shesha-reactjs/src/components/entityPicker/index.tsx @@ -11,6 +11,7 @@ import { EntityPickerModal } from './modal'; import { getValueByPropertyName } from '@/utils/object'; import { SheshaError } from '@/utils/errors'; import { addPx } from '@/utils/style'; +import { useAvailableConstantsData } from '@/providers/form/utils'; const EntityPickerReadOnly = (props: IEntityPickerProps): JSX.Element => { const { entityType, displayEntityKey, value } = props; @@ -71,6 +72,7 @@ const EntityPickerEditable = (props: IEntityPickerProps): JSX.Element => { const { styles } = useStyles({ style }); const selectRef = useRef(undefined); + const allData = useAvailableConstantsData(); const [showModal, setShowModal] = useState(false); @@ -227,7 +229,7 @@ const EntityPickerEditable = (props: IEntityPickerProps): JSX.Element => { paddingBottom, paddingLeft, borderLeftStyle: dividerStyle?.style ?? 'solid', - borderLeftWidth: addPx(dividerStyle?.width) ?? '1px', + borderLeftWidth: addPx(dividerStyle?.width, allData) ?? '1px', borderLeftColor: dividerStyle?.color ?? '#d9d9d9', borderRadius: `0px ${borderRadii?.[1]} ${borderRadii?.[2]} 0px`, height: '100%', diff --git a/shesha-reactjs/src/components/entityPicker/styles/styles.ts b/shesha-reactjs/src/components/entityPicker/styles/styles.ts index fd86c9aee0..708e5bddba 100644 --- a/shesha-reactjs/src/components/entityPicker/styles/styles.ts +++ b/shesha-reactjs/src/components/entityPicker/styles/styles.ts @@ -74,6 +74,7 @@ export const useStyles = createStyles(({ css, cx, prefixCls, token }, { style }: const entitySelect = cx("entity-select", css` --ant-color-text: ${style?.color || '#000'} !important; width: calc(100% - 32px) !important; + flex-basis: unset !important; &:hover { border-color: ${token.colorPrimary} !important; } diff --git a/shesha-reactjs/src/components/entityReference/index.tsx b/shesha-reactjs/src/components/entityReference/index.tsx index f9e3e765d9..295e66c248 100644 --- a/shesha-reactjs/src/components/entityReference/index.tsx +++ b/shesha-reactjs/src/components/entityReference/index.tsx @@ -259,7 +259,7 @@ export const EntityReference: FC = (props) => { Boolean(props.additionalProperties) && props.additionalProperties?.length > 0 && props.additionalProperties.some((p) => p.key === 'id') ? props.additionalProperties : [{ key: 'id', value: '{{entityReference.id}}' }], - modalWidth: addPx(props.modalWidth), + modalWidth: addPx(props.modalWidth, executionContext), skipFetchData: props.skipFetchData ?? false, submitHttpVerb: props.submitHttpVerb ?? 'PUT', }, @@ -356,7 +356,7 @@ export const EntityReference: FC = (props) => { entityId={entityId} entityType={entityType} getEntityUrl={props.getEntityUrl} - width={addPx(cappedWidth)} + width={addPx(cappedWidth, executionContext)} formIdentifier={formIdentifier} formType={formType} // Pass formArguments with entity ID to enable form's data loader (same algorithm as dialog mode) @@ -375,7 +375,7 @@ export const EntityReference: FC = (props) => { {displayTextByType} ); - }, [fetched, styles, props, displayText, formIdentifier, entityId, displayTextByType, dialogExecute, properties, entityType, formType]); + }, [fetched, styles, props, displayText, formIdentifier, entityId, displayTextByType, dialogExecute, properties, entityType, formType, executionContext]); if (props.formSelectionMode === 'name' && !props.formIdentifier) return ( diff --git a/shesha-reactjs/src/components/fileUpload/index.tsx b/shesha-reactjs/src/components/fileUpload/index.tsx index 83770e64e5..8aad045e69 100644 --- a/shesha-reactjs/src/components/fileUpload/index.tsx +++ b/shesha-reactjs/src/components/fileUpload/index.tsx @@ -221,7 +221,7 @@ export const FileUpload: FC = ({ accept: allowedFileTypes?.join(','), multiple: false, fileList: fileInfo ? [fileInfo] : [], - style: !isDragger && stylesProp, + style: !isDragger ? stylesProp : undefined, customRequest: onCustomRequest, beforeUpload: (file) => { if (!isFileTypeAllowed(file.name, allowedFileTypes)) { diff --git a/shesha-reactjs/src/components/formDesigner/components/configurableFormItemLive.tsx b/shesha-reactjs/src/components/formDesigner/components/configurableFormItemLive.tsx index 03e93879b1..77a4e69427 100644 --- a/shesha-reactjs/src/components/formDesigner/components/configurableFormItemLive.tsx +++ b/shesha-reactjs/src/components/formDesigner/components/configurableFormItemLive.tsx @@ -1,11 +1,13 @@ import React, { FC, useMemo } from 'react'; import { Form, FormItemProps } from 'antd'; -import { getFieldNameFromExpression, getValidationRules } from '@/providers/form/utils'; +import { getFieldNameFromExpression, getValidationRules, useAvailableConstantsData } from '@/providers/form/utils'; import classNames from 'classnames'; import { useFormItem, useShaFormInstance } from '@/providers'; import { IConfigurableFormItemProps } from './model'; import { ConfigurableFormItemContext } from './configurableFormItemContext'; import { ConfigurableFormItemForm } from './configurableFormItemForm'; +import { designerConstants } from '../utils/designerConstants'; +import { addPx } from '@/utils/style'; export const ConfigurableFormItemLive: FC = ({ children, @@ -20,6 +22,9 @@ export const ConfigurableFormItemLive: FC = ({ const getFormData = getPublicFormApi().getFormData; const formItem = useFormItem(); const { namePrefix, wrapperCol: formItemWrapperCol, labelCol: formItemlabelCol } = formItem; + const shaForm = useShaFormInstance(); + const isInDesigner = shaForm.formMode === 'designer'; + const allData = useAvailableConstantsData(); const layout = useMemo(() => { // Make sure the `wrapperCol` and `labelCol` from `FormItemProver` override the ones from the main form @@ -29,6 +34,22 @@ export const ConfigurableFormItemLive: FC = ({ const { hideLabel, hidden } = model; if (hidden) return null; + const { top: defaultMarginTop, left: defaultMarginLeft, right: defaultMarginRight, bottom: defaultMarginBottom } = designerConstants.DEFAULT_FORM_ITEM_MARGINS; + + // In designer mode: NEVER apply margins to Form.Item (wrapper handles them) + // In live mode: Apply margins from allStyles.margins or use defaults + // Note: margins are stored separately so inner components don't get them (prevents double margins) + const rawMargins = isInDesigner + ? { marginTop: 0, marginBottom: 0, marginLeft: 0, marginRight: 0 } + : (model?.allStyles?.margins || {}); + + const { + marginTop = defaultMarginTop, + marginBottom = defaultMarginBottom, + marginRight = defaultMarginRight, + marginLeft = defaultMarginLeft, + } = rawMargins; + const propName = namePrefix && !model.initialContext ? namePrefix + '.' + model.propertyName : model.propertyName; @@ -46,6 +67,12 @@ export const ConfigurableFormItemLive: FC = ({ wrapperCol: hideLabel ? { span: 24 } : layout?.wrapperCol, // layout: model.layout, this property appears to have been removed from the Ant component name: model.context ? undefined : getFieldNameFromExpression(propName), + style: { + marginTop: addPx(marginTop, allData), + marginBottom: addPx(marginBottom, allData), + marginRight: addPx(marginRight, allData), + marginLeft: addPx(marginLeft, allData), + }, }; if (typeof children === 'function') { diff --git a/shesha-reactjs/src/components/formDesigner/components/configurableFormItemSetting.tsx b/shesha-reactjs/src/components/formDesigner/components/configurableFormItemSetting.tsx index 14ee2806d9..a23c273840 100644 --- a/shesha-reactjs/src/components/formDesigner/components/configurableFormItemSetting.tsx +++ b/shesha-reactjs/src/components/formDesigner/components/configurableFormItemSetting.tsx @@ -5,6 +5,8 @@ import { getPropertySettingsFromData } from '@/designer-components/_settings/uti import { SettingsControl, useShaFormInstance } from '@/index'; import { IConfigurableFormItemChildFunc, IConfigurableFormItemProps } from './model'; import { ConfigurableFormItemLive } from './configurableFormItemLive'; +import { useStyles } from './styles'; +import classNames from 'classnames'; export const ConfigurableFormItemSetting: FC = ({ children, @@ -12,7 +14,7 @@ export const ConfigurableFormItemSetting: FC = ({ valuePropName, }) => { const { formData } = useShaFormInstance(); - + const { styles } = useStyles(); if (model.hidden) return null; const { _mode: mode } = getPropertySettingsFromData(formData, model.propertyName); @@ -51,7 +53,7 @@ export const ConfigurableFormItemSetting: FC = ({ validate: { required: model.validate?.required }, hidden: model.hidden, }} - className="sha-js-label" + className={classNames(styles.settingsFormItem, "sha-js-label")} labelCol={{ span: 24 }} wrapperCol={{ span: 24 }} > diff --git a/shesha-reactjs/src/components/formDesigner/components/model.ts b/shesha-reactjs/src/components/formDesigner/components/model.ts index 0bba5ef6c1..8b19249f38 100644 --- a/shesha-reactjs/src/components/formDesigner/components/model.ts +++ b/shesha-reactjs/src/components/formDesigner/components/model.ts @@ -1,4 +1,4 @@ -import { IConfigurableFormComponent } from "@/index"; +import { IComponentModelProps } from "@/index"; import { ColProps } from "antd"; import { ReactNode } from "react"; @@ -9,7 +9,7 @@ export type IConfigurableFormItemChildFunc = ( ) => ReactNode; export interface IConfigurableFormItemProps { - model: IConfigurableFormComponent; + model: IComponentModelProps; readonly children?: ReactNode | IConfigurableFormItemChildFunc; className?: string; valuePropName?: string; diff --git a/shesha-reactjs/src/components/formDesigner/components/styles.ts b/shesha-reactjs/src/components/formDesigner/components/styles.ts new file mode 100644 index 0000000000..eb7fbe20ad --- /dev/null +++ b/shesha-reactjs/src/components/formDesigner/components/styles.ts @@ -0,0 +1,11 @@ +import { createStyles } from '@/styles'; + +export const useStyles = createStyles(({ css, cx }) => { + const settingsFormItem = cx(css` + margin: 0px !important; + `); + + return { + settingsFormItem, + }; +}); diff --git a/shesha-reactjs/src/components/formDesigner/configurableFormComponent/index.tsx b/shesha-reactjs/src/components/formDesigner/configurableFormComponent/index.tsx index ed5718a2d5..568740696d 100644 --- a/shesha-reactjs/src/components/formDesigner/configurableFormComponent/index.tsx +++ b/shesha-reactjs/src/components/formDesigner/configurableFormComponent/index.tsx @@ -6,31 +6,38 @@ import React, { MutableRefObject, memo, useMemo, + useRef, } from 'react'; import { createPortal } from 'react-dom'; import ValidationIcon from './validationIcon'; -import { DataContextTopLevels, EditMode, IConfigurableFormComponent } from '@/providers'; +import { DataContextTopLevels, EditMode, IComponentModelProps, IConfigurableFormComponent, useCanvas } from '@/providers'; import { EditOutlined, EyeInvisibleOutlined, FunctionOutlined, StopOutlined, } from '@ant-design/icons'; -import { getActualPropertyValue, useAvailableConstantsData } from '@/providers/form/utils'; +import { getActualPropertyValue, getStyle, useAvailableConstantsData } from '@/providers/form/utils'; import { isPropertySettings } from '@/designer-components/_settings/utils'; import { Show } from '@/components/show'; import { Tooltip } from 'antd'; import { ShaForm, useIsDrawingForm } from '@/providers/form'; + import { useFormDesigner, useFormDesignerReadOnly, useFormDesignerSelectedComponentId } from '@/providers/formDesigner'; import { useStyles } from '../styles/styles'; import { ComponentProperties } from '../componentPropertiesPanel/componentProperties'; import { useFormDesignerComponentGetter } from '@/providers/form/hooks'; +import { useFormComponentStyles } from '@/hooks/formComponentHooks'; +import { dimensionUtils } from '../utils/dimensionUtils'; +import { stylingUtils } from '../utils/stylingUtils'; +import { designerConstants } from '../utils/designerConstants'; +import { jsonSafeParse } from '@/utils/object'; export interface IConfigurableFormComponentDesignerProps { - componentModel: IConfigurableFormComponent; + componentModel: IComponentModelProps; selectedComponentId?: string; readOnly?: boolean; - settingsPanelRef?: MutableRefObject; + settingsPanelRef?: MutableRefObject; hidden?: boolean; componentEditMode?: EditMode; } @@ -43,11 +50,69 @@ const ConfigurableFormComponentDesignerInner: FC { const { styles } = useStyles(); - const getToolboxComponent = useFormDesignerComponentGetter(); + const { activeDevice } = useCanvas(); + const formItemRef = useRef(null); - const isSelected = componentModel.id && selectedComponentId === componentModel.id; + // Memoize component lookup to prevent unnecessary re-renders + const component = useMemo(() => getToolboxComponent(componentModel?.type), [getToolboxComponent, componentModel?.type]); + // Extract primitive values for stable dependencies - avoid object recreation triggering re-renders + const preserveDimensionsInDesigner = useMemo(() => component?.preserveDimensionsInDesigner, [component]); + + /** + * Merges component model with device-specific settings. + * Handles the complexity of components with separate container configurations + * (e.g., attachmentsEditor with thumbnail dimensions at root and container dimensions in container property). + */ + const fullComponentModel = useMemo(() => { + const deviceModel = componentModel?.[activeDevice]; + + // Determine if component has separate container-level styling + // This is true when both root-level and container-level dimensions exist + const hasRootDimensions = !!(deviceModel?.dimensions || componentModel?.dimensions); + const hasContainerDimensions = !!(deviceModel?.container?.dimensions || componentModel?.container?.dimensions); + const hasSeparateContainerStyles = hasRootDimensions && hasContainerDimensions; + + // For backward compatibility: spread container props to root UNLESS component has explicit separate styles + const containerPropsToSpread = hasSeparateContainerStyles + ? {} + : { ...componentModel?.container, ...deviceModel?.container }; + + // Always merge container objects to preserve container-level configuration + const mergedContainer = { + ...componentModel?.container, + ...deviceModel?.container, + }; + + return { + ...componentModel, + ...deviceModel, + ...containerPropsToSpread, + container: mergedContainer, + }; + }, [componentModel, activeDevice]); + /** + * Determines which model to use for calculating wrapper styles. + * When container has its own dimensions, those take precedence for wrapper sizing. + */ + const styleModelForWrapper = useMemo(() => { + const hasContainerDimensions = !!fullComponentModel.container?.dimensions; + return hasContainerDimensions + ? { ...fullComponentModel, ...fullComponentModel.container } + : fullComponentModel; + }, [fullComponentModel]); + const { dimensionsStyles, stylingBoxAsCSS, jsStyle } = useFormComponentStyles(styleModelForWrapper); + + // Extract margins from ORIGINAL component styling (before stripping) for the wrapper + // Custom style margins take precedence over stylingBox margins + const originalJsStyle = useMemo(() => { + return componentModel.type === 'container' + ? getStyle(fullComponentModel?.wrapperStyle) + : getStyle(fullComponentModel.style); + }, [fullComponentModel, componentModel.type]); + + const isSelected = componentModel.id && selectedComponentId === componentModel.id; const invalidConfiguration = componentModel.settingsValidationErrors && componentModel.settingsValidationErrors.length > 0; const hiddenFx = isPropertySettings(componentModel.hidden); @@ -56,38 +121,145 @@ const ConfigurableFormComponentDesignerInner: FC { - const renderRerquired = isSelected && settingsPanelRef.current; + const renderRequired = isSelected && settingsPanelRef?.current; - if (!renderRerquired) + if (!renderRequired) return null; - const createPortalInner = true - ? createPortal - : (a) => a; - const result = createPortalInner(( + const result = createPortal((
e.stopPropagation()} onMouseOver={(e) => e.stopPropagation()} onMouseOut={(e) => e.stopPropagation()} >
), settingsPanelRef.current, "propertiesPanel"); return result; - }, [isSelected]); + }, [isSelected, settingsPanelRef, readOnly, component]); + + // Extract margins from ORIGINAL component styling (both stylingBox and custom styles) + // Custom style margins take precedence over stylingBox margins + const margins = useMemo( + () => stylingUtils.extractMargins(originalJsStyle, stylingBoxAsCSS), + [originalJsStyle, stylingBoxAsCSS], + ); + + // Get component dimensions (handles special cases like DataTable context) + // Memoized because getComponentDimensions creates new objects and computes flexBasis logic + const componentDimensions = useMemo(() => { + // For components where dimensions apply to inner elements, wrapper uses auto dimensions + // Check if all dimensions are being preserved (true) vs partial preservation + const preservingAllDimensions = preserveDimensionsInDesigner === true; + if (preservingAllDimensions) { + return { width: 'auto', height: 'auto' }; + } + + // Use dimensionUtils which handles partial preservation (array case) + return dimensionUtils.getComponentDimensions( + preserveDimensionsInDesigner, + dimensionsStyles, + jsStyle, + ); + }, [preserveDimensionsInDesigner, dimensionsStyles, jsStyle]); + + // Create the model for rendering - components receive dimensions based on their config + // and no margins (since wrapper handles margins directly) + // Note: fullComponentModel already has margins stripped from style property + const renderComponentModel = useMemo(() => { + const deviceDimensions = dimensionUtils.getDeviceDimensions(); + // In designer mode, component only gets padding (margins go to wrapper) + const stylingBoxWithPaddingOnly = stylingUtils.createPaddingOnlyStylingBox(fullComponentModel.stylingBox); + const stylingBoxWithPaddingOnlyParsed = jsonSafeParse>(stylingBoxWithPaddingOnly, {}); + + // Determine preservation mode + const preservingAll = preserveDimensionsInDesigner === true; + const preservingSome = Array.isArray(preserveDimensionsInDesigner) && preserveDimensionsInDesigner.length > 0; + + // Helper to get designer dimensions based on original config + // If preserveDimensionsInDesigner is true, use original dimensions; otherwise fill wrapper + const getDesignerDimensions = (originalDims?: typeof fullComponentModel.dimensions): typeof deviceDimensions | undefined => { + // If all dimensions are preserved, return original + if (preservingAll) return originalDims; + + // If component has container dimensions, preserve original thumbnail dimensions + // The wrapper will use container dimensions instead + if (fullComponentModel.container?.dimensions) return originalDims; + + // If component has custom dimension calculation, use it + if (component?.getDesignerDimensions) { + return component.getDesignerDimensions(originalDims, deviceDimensions); + } + + // Default: fill the wrapper + return deviceDimensions; + }; + + // Helper to get component dimensions (what the inner component receives) + // Components always fill 100% of their wrapper in designer mode (wrapper handles sizing) + const getComponentDimensions = (originalDims?: typeof fullComponentModel.dimensions): React.CSSProperties => { + // If all dimensions are preserved, merge with device dimensions + if (preservingAll) return { ...deviceDimensions, ...originalDims }; + + // If component has container dimensions, preserve original thumbnail dimensions for the component + // The wrapper will use container dimensions instead + if (fullComponentModel.container?.dimensions) return originalDims ?? deviceDimensions; + + // For partial preservation, use the dimensionUtils to handle the logic + if (preservingSome) { + return dimensionUtils.getComponentDimensionsForMode( + preserveDimensionsInDesigner, + originalDims || {}, + true, // isDesignerMode + ); + } + + // All components fill the wrapper in designer mode + return deviceDimensions; + }; + + // Set dimensions for device-specific configs + // fullComponentModel already has margins stripped from style properties + return { + ...fullComponentModel, + dimensions: getDesignerDimensions(fullComponentModel.dimensions), + stylingBox: stylingBoxWithPaddingOnly, + + allStyles: { + ...fullComponentModel.allStyles, + fullStyle: { ...fullComponentModel.allStyles?.fullStyle, stylingBoxAsCSS: stylingBoxWithPaddingOnlyParsed }, + ...getStyle(fullComponentModel.style), + stylingBoxAsCSS: stylingBoxWithPaddingOnlyParsed, + // Component dimensions: components with preserveDimensionsInDesigner get original dims, others fill wrapper + // Use dimensionsStyles (includes min/max) instead of fullComponentModel.dimensions (only width/height) + dimensionsStyles: getComponentDimensions(dimensionsStyles), + stylingBox: stylingBoxWithPaddingOnly, + }, + }; + }, [fullComponentModel, component, preserveDimensionsInDesigner, dimensionsStyles]); + + // Create wrapper style - owns dimensions and margins + const rootContainerStyle = useMemo(() => { + return stylingUtils.createRootContainerStyle(componentDimensions, margins); + }, [componentDimensions, margins]); return (
@@ -107,6 +279,7 @@ const ConfigurableFormComponentDesignerInner: FC + @@ -115,10 +288,11 @@ const ConfigurableFormComponentDesignerInner: FC {invalidConfiguration && } -
+ +
-
- +
+
@@ -134,8 +308,18 @@ export const ConfigurableFormComponentDesigner: FC