= (props) => {
containerId={item.id}
gap={gap}
wrapperStyle={{
- padding: addPx(item.padding || 0),
- maxWidth: vertical ? '100%' : addPx(item.width),
+ padding: addPx(item.padding || 0, allData),
+ maxWidth: vertical ? '100%' : addPx(item.width, allData),
boxSizing: 'border-box',
}}
style={containerStyle(item)}
diff --git a/shesha-reactjs/src/components/mainLayout/styles/indexStyles.ts b/shesha-reactjs/src/components/mainLayout/styles/indexStyles.ts
index c8c07eb5d1..c86eaf0262 100644
--- a/shesha-reactjs/src/components/mainLayout/styles/indexStyles.ts
+++ b/shesha-reactjs/src/components/mainLayout/styles/indexStyles.ts
@@ -1,172 +1,171 @@
-import { sheshaStyles } from '@/styles';
-import { createGlobalStyle } from 'antd-style';
-
-const shaBorder = '1px solid #d3d3d3'; // @sha-border
-const shaPageHeadingHeight = '45px';
-const layoutHeaderHeight = '55px';
-
-export const GlobalSheshaStyles = createGlobalStyle`
- .sha-header-configuration {
- .sha-components-container-inner {
- display: flex;
- align-items: center;
-
- min-height: ${shaPageHeadingHeight};
- background: white;
-
- &.fixed-heading {
- position: sticky;
- z-index: 1;
- top: ${layoutHeaderHeight};
- }
- }
- }
-
- .sha-index-table-full {
- .sha-react-table {
- margin-top: ${sheshaStyles.paddingLG}px;
- }
- .sha-index-table {
- margin: 0 ${sheshaStyles.paddingLG}px;
- }
- }
-
- .sha-index-toolbar {
- border-bottom: ${shaBorder};
-
- background: white;
- max-height: ${sheshaStyles.pageHeadingHeight} !important;
-
- .sha-components-container-inner {
- display: flex !important;
- }
-
- ${sheshaStyles.flexCenterAlignedSpaceBetween}
- width: 100%;
- padding: 0 ${sheshaStyles.paddingLG}px;
-
- &.sha-paging-height {
- height: 46px;
- }
-
- .sha-index-toolbar-left {
- button {
- padding-left: unset;
- }
- }
- }
-
- .${(p) => p.theme.prefixCls}-select-dropdown--multiple {
- .${(p) => p.theme.prefixCls}-select-dropdown-menu {
- .${(p) => p.theme.prefixCls}-select-dropdown-menu-item {
- &.${(p) => p.theme.prefixCls}-select-dropdown-menu-item-selected {
- display: none;
- }
- }
- }
- }
-
- .${(p) => p.theme.prefixCls}-select-dropdown {
- .${(p) => p.theme.prefixCls}-select-item {
- &.${(p) => p.theme.prefixCls}-select-item-option-selected {
- display: none;
- }
- }
- }
-
- .${(p) => p.theme.prefixCls}-form-item-label {
- white-space: normal;
- font-weight: bold;
- }
- .${(p) => p.theme.prefixCls}-form-item {
- margin-bottom: 0 !important;
-
- .${(p) => p.theme.prefixCls}-row {
- &.${(p) => p.theme.prefixCls}-form-item-row {
- margin-bottom: 5px;
- }
- }
- }
-
- .sha-form-settings-editor .ant-form-item .ant-row.ant-form-item-row {
- margin-bottom: 0px !important;
- .ant-form-item-control-input{
- display: inline !important;
- }
- }
-
- .sha-form-designer {
- .sha-index-toolbar {
- max-height: unset !important;
- }
- }
-
- .${(p) => p.theme.prefixCls}-row {
- &.${(p) => p.theme.prefixCls}-form-item {
- margin-bottom: 5px;
- }
- }
-
- .${(p) => p.theme.prefixCls}-space {
- flex-wrap: wrap;
- }
-
- .${(p) => p.theme.prefixCls}-form {
- .${(p) => p.theme.prefixCls}-alert {
- margin-bottom: 5px;
- }
- }
-
- .${(p) => p.theme.prefixCls}-tree-list {
- padding: 18px 0;
- }
-
- .${(p) => p.theme.prefixCls}-dropdown-menu {
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif, 'Apple Color Emoji',
- 'Segoe UI Emoji';
- }
-
- body {
- margin: 0;
- div {
- .ant-dropdown-menu-submenu-title {
- display: flex !important;
- align-items: center !important;
- }
- }
- }
-
- [hidden] {
- display: none !important;
- }
- .${(p) => p.theme.prefixCls}-btn:empty {
- display: inline-block;
- width: 0;
- visibility: hidden;
- content: '\a0';
- }
- .sha-required-mark {
- margin-left: 4px;
- color: ${(p) => p.theme.colorErrorText};
- font-family: ${(p) => p.theme.fontFamily};
- line-height: 1;
- }
- .sha-toolbar-btn-configurable {
- display: flex;
- align-items: center;
- span {
- max-width: 100%;
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
- }
-
- }
-
- .${(p) => p.theme.prefixCls}-modal-root {
- .${(p) => p.theme.prefixCls}-modal {
- top: 100px !important;
- vertical-align: top !important;
- }
- }
-`;
+import { sheshaStyles } from '@/styles';
+import { createGlobalStyle } from 'antd-style';
+
+const shaBorder = '1px solid #d3d3d3'; // @sha-border
+const shaPageHeadingHeight = '45px';
+const layoutHeaderHeight = '55px';
+
+export const GlobalSheshaStyles = createGlobalStyle`
+ .sha-header-configuration {
+ .sha-components-container-inner {
+ display: flex;
+ align-items: center;
+
+ min-height: ${shaPageHeadingHeight};
+ background: white;
+
+ &.fixed-heading {
+ position: sticky;
+ z-index: 1;
+ top: ${layoutHeaderHeight};
+ }
+ }
+ }
+
+ .sha-index-table-full {
+ .sha-react-table {
+ margin-top: ${sheshaStyles.paddingLG}px;
+ }
+ .sha-index-table {
+ margin: 0 ${sheshaStyles.paddingLG}px;
+ }
+ }
+
+ .sha-index-toolbar {
+ border-bottom: ${shaBorder};
+
+ background: white;
+ max-height: ${sheshaStyles.pageHeadingHeight} !important;
+
+ .sha-components-container-inner {
+ display: flex !important;
+ }
+
+ ${sheshaStyles.flexCenterAlignedSpaceBetween}
+ width: 100%;
+ padding: 0 ${sheshaStyles.paddingLG}px;
+
+ &.sha-paging-height {
+ height: 46px;
+ }
+
+ .sha-index-toolbar-left {
+ button {
+ padding-left: unset;
+ }
+ }
+ }
+
+ .${(p) => p.theme.prefixCls}-select-dropdown--multiple {
+ .${(p) => p.theme.prefixCls}-select-dropdown-menu {
+ .${(p) => p.theme.prefixCls}-select-dropdown-menu-item {
+ &.${(p) => p.theme.prefixCls}-select-dropdown-menu-item-selected {
+ display: none;
+ }
+ }
+ }
+ }
+
+ .${(p) => p.theme.prefixCls}-select-dropdown {
+ .${(p) => p.theme.prefixCls}-select-item {
+ &.${(p) => p.theme.prefixCls}-select-item-option-selected {
+ display: none;
+ }
+ }
+ }
+
+ .${(p) => p.theme.prefixCls}-form-item-label {
+ white-space: normal;
+ font-weight: bold;
+ }
+ .${(p) => p.theme.prefixCls}-form-item {
+ margin: 0 !important;
+
+ .${(p) => p.theme.prefixCls}-row {
+ &.${(p) => p.theme.prefixCls}-form-item-row {
+ margin-bottom: 5px;
+ }
+ }
+ }
+
+ .sha-form-settings-editor .ant-form-item .ant-row.ant-form-item-row {
+ margin-bottom: 0px !important;
+ .${(p) => p.theme.prefixCls}-form-item-control-input {
+ display: inline !important;
+ }
+ }
+
+ .sha-form-designer {
+ .sha-index-toolbar {
+ max-height: unset !important;
+ }
+ }
+
+ .${(p) => p.theme.prefixCls}-row {
+ &.${(p) => p.theme.prefixCls}-form-item {
+ margin-bottom: 5px;
+ }
+ }
+
+ .${(p) => p.theme.prefixCls}-space {
+ flex-wrap: wrap;
+ }
+
+ .${(p) => p.theme.prefixCls}-form {
+ .${(p) => p.theme.prefixCls}-alert {
+ margin-bottom: 5px;
+ }
+ }
+
+ .${(p) => p.theme.prefixCls}-tree-list {
+ padding: 18px 0;
+ }
+
+ .${(p) => p.theme.prefixCls}-dropdown-menu {
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif, 'Apple Color Emoji',
+ 'Segoe UI Emoji';
+ }
+
+ body {
+ margin: 0;
+ div {
+ .${(p) => p.theme.prefixCls}-dropdown-menu-submenu-title {
+ display: flex !important;
+ align-items: center !important;
+ }
+ }
+ }
+
+ [hidden] {
+ display: none !important;
+ }
+ .${(p) => p.theme.prefixCls}-btn:empty {
+ display: inline-block;
+ width: 0;
+ visibility: hidden;
+ }
+ .sha-required-mark {
+ margin-left: 4px;
+ color: ${(p) => p.theme.colorErrorText};
+ font-family: ${(p) => p.theme.fontFamily};
+ line-height: 1;
+ }
+ .sha-toolbar-btn-configurable {
+ display: flex;
+ align-items: center;
+ span {
+ max-width: 100%;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ }
+
+ }
+
+ .${(p) => p.theme.prefixCls}-modal-root {
+ .${(p) => p.theme.prefixCls}-modal {
+ top: 100px !important;
+ vertical-align: top !important;
+ }
+ }
+`;
diff --git a/shesha-reactjs/src/components/panel/styles/styles.ts b/shesha-reactjs/src/components/panel/styles/styles.ts
index 9728d164f2..5f54cd4a29 100644
--- a/shesha-reactjs/src/components/panel/styles/styles.ts
+++ b/shesha-reactjs/src/components/panel/styles/styles.ts
@@ -1,4 +1,4 @@
-import { createStyles } from '@/styles';
+import { createStyles, sheshaStyles } from '@/styles';
import { CSSProperties } from 'react';
export const useStyles = createStyles(({ css, cx, token, prefixCls }, {
@@ -103,25 +103,27 @@ export const useStyles = createStyles(({ css, cx, token, prefixCls }, {
--ant-line-width: ${hasBorder ? '0px' : '1px'} !important;
${hasBorder && '--ant-line-width: 0px !important;'}
--ant-collapse-header-bg: transparent !important;
+ ${width ? `width: ${width};` : ''}
+ ${minWidth ? `min-width: ${minWidth};` : ''}
+ ${maxWidth ? `max-width: ${maxWidth};` : ''}
+ ${height ? `height: ${height};` : ''}
+ ${minHeight ? `min-height: ${minHeight};` : ''}
+ ${maxHeight ? `max-height: ${maxHeight};` : ''}
+ ${marginBottom ? `margin-bottom: ${marginBottom};` : ''}
+ ${marginTop ? `margin-top: ${marginTop};` : ''}
+ ${marginLeft ? `margin-left: ${marginLeft};` : ''}
+ ${marginRight ? `margin-right: ${marginRight};` : ''}
> .ant-collapse-item {
display: flex;
flex-direction: column;
box-shadow: ${boxShadow};
border-radius: ${borderTopLeftRadius} ${borderTopRightRadius} ${borderBottomRightRadius} ${borderBottomLeftRadius} !important;
- margin-bottom: ${marginBottom};
- margin-top: ${marginTop};
- margin-left: ${marginLeft};
- margin-right: ${marginRight};
+ height: 100%;
}
> .ant-collapse-item > .ant-collapse-content {
- width: ${width};
- min-width: ${minWidth};
- max-width: ${maxWidth};
- height: ${height};
- min-height: ${minHeight};
- max-height: ${maxHeight};
+ flex: 1;
background: ${backgroundImage || backgroundColor};
background-size: ${backgroundSize};
background-position: ${backgroundPosition};
@@ -136,14 +138,15 @@ export const useStyles = createStyles(({ css, cx, token, prefixCls }, {
border-right: ${ghost ? 'none' : borderRightWidth || borderWidth} ${borderRightStyle || borderStyle} ${borderRightColor || borderColor};
border-left: ${ghost ? 'none' : borderLeftWidth || borderWidth} ${borderLeftStyle || borderStyle} ${borderLeftColor || borderColor};
border-bottom: ${ghost ? 'none' : borderBottomWidth || borderWidth} ${borderBottomStyle || borderStyle} ${borderBottomColor || borderColor};
+ overflow: auto;
+ ${sheshaStyles.thinScrollbars}
> .ant-collapse-content-box {
--ant-collapse-content-padding: 0px !important;
padding: 0px !important;
- height: 100%;
width: 100%;
- overflow: ${overflow?.overflow ?? 'auto'};
- ${overflow};
+ height: 100%;
+ overflow: ${typeof overflow === 'object' ? (overflow?.overflow ?? 'auto') : (overflow ?? 'auto')};
}
}
@@ -160,12 +163,10 @@ export const useStyles = createStyles(({ css, cx, token, prefixCls }, {
position: relative;
visibility: ${hideCollapseContent ? 'hidden' : 'visible'};
background: ${headerBgImage || headerBgColor};
- width: ${width};
+ width: 100%;
background-size: ${headerBackgroundSize};
background-repeat: ${headerBackgroundRepeat};
background-position: ${headerBackgroundPosition};
- min-width: ${minWidth};
- max-width: ${maxWidth};
height: ${headerHeight};
min-height: ${headerMinHeight};
max-height: ${headerMaxHeight};
@@ -209,10 +210,6 @@ export const useStyles = createStyles(({ css, cx, token, prefixCls }, {
border-bottom: 2px solid ${token.colorPrimary};
${accentStyle && `border-top: 3px solid var(--primary-color);`}
font-weight: ${fontWeight || '500'};
-
- .ant-collapse-header-text {
- margin-left: -8px;
- }
}
> .ant-collapse-content {
border: none;
@@ -253,7 +250,7 @@ export const useStyles = createStyles(({ css, cx, token, prefixCls }, {
height: max-content;
min-height: ${minHeight};
max-height: ${maxHeight};
- overflow: ${overflow ?? 'auto'};
+ overflow: ${typeof overflow === 'object' ? (overflow?.overflow ?? 'auto') : (overflow ?? 'auto')};
padding-top: ${paddingTop} !important;
padding-bottom: ${paddingBottom} !important;
padding-left: ${paddingLeft} !important;
diff --git a/shesha-reactjs/src/components/reactTable/index.tsx b/shesha-reactjs/src/components/reactTable/index.tsx
index 720a5051b2..d91ff3e910 100644
--- a/shesha-reactjs/src/components/reactTable/index.tsx
+++ b/shesha-reactjs/src/components/reactTable/index.tsx
@@ -602,7 +602,7 @@ export const ReactTable: FC
= ({
if (!cellRect) return { top: 0, left: 0 };
// Get the canvas zoom level (default to 100 if not available)
- const zoomLevel = canvasState?.zoom ?? 100;
+ const zoomLevel = shaForm?.formMode === "designer" ? canvasState?.zoom ?? 100 : 100;
const zoomScale = Math.max(0.01, zoomLevel / 100);
const viewport = {
diff --git a/shesha-reactjs/src/components/reactTable/styles/styles.ts b/shesha-reactjs/src/components/reactTable/styles/styles.ts
index 90490da48a..f7fd239b23 100644
--- a/shesha-reactjs/src/components/reactTable/styles/styles.ts
+++ b/shesha-reactjs/src/components/reactTable/styles/styles.ts
@@ -196,7 +196,6 @@ export const useMainStyles = createStyles(({ css, cx, token, prefixCls, iconPref
*/
display: inline-block;
width: calc(100% - 16px);
- overflow-x: auto;
/* These styles are required for a horizontaly scrollable table overflow */
/* IMPORTANT: freezeHeaders requires overflow: auto for position: sticky to work */
overflow-y: ${freezeHeaders ? 'auto' : (boxShadow ? 'visible' : 'auto')};
@@ -230,6 +229,8 @@ export const useMainStyles = createStyles(({ css, cx, token, prefixCls, iconPref
border-spacing: 0;
display: inline-block;
min-width: 100%;
+ overflow-x: auto;
+
/* Background applied to table ensures it covers all rows when scrolling with freezeHeaders */
${backgroundColor ? `background: ${backgroundColor};` : 'background: white;'}
diff --git a/shesha-reactjs/src/components/refListStatus/index.tsx b/shesha-reactjs/src/components/refListStatus/index.tsx
index 8e6981daf0..68b96b9359 100644
--- a/shesha-reactjs/src/components/refListStatus/index.tsx
+++ b/shesha-reactjs/src/components/refListStatus/index.tsx
@@ -15,6 +15,7 @@ export interface IRefListStatusProps {
showReflistName?: boolean;
style?: CSSProperties;
value?: any;
+ isDesigner?: boolean;
}
const Icon = ({ type, ...rest }): JSX.Element => {
@@ -31,6 +32,7 @@ export const RefListStatus: FC = (props) => {
solidBackground,
showReflistName,
style = {},
+ isDesigner = false,
} = props;
const { width, height, minHeight, minWidth, maxHeight, maxWidth } = style;
const dimensionsStyles = { width, height, minHeight, minWidth, maxHeight, maxWidth };
@@ -54,9 +56,26 @@ export const RefListStatus: FC = (props) => {
const canShowIcon = showIcon && itemData?.icon;
- if (typeof itemData?.itemValue === 'undefined' && !listItem?.loading) return null;
+ // In designer mode, show a placeholder when there's no value or data
+ if (typeof itemData?.itemValue === 'undefined' && !listItem?.loading) {
+ if (isDesigner) {
+ return (
+
+
+ {showReflistName ? 'Reference List Item' : 'N/A'}
+
+
+ );
+ }
+ return null;
+ }
- return listItem?.loading ? (
+ return listItem?.loading || !itemData ? (
) : (
diff --git a/shesha-reactjs/src/components/refListStatus/styles/styles.ts b/shesha-reactjs/src/components/refListStatus/styles/styles.ts
index 91466b310d..7b9d9f9919 100644
--- a/shesha-reactjs/src/components/refListStatus/styles/styles.ts
+++ b/shesha-reactjs/src/components/refListStatus/styles/styles.ts
@@ -1,6 +1,6 @@
import { createStyles } from '@/styles';
-export const useStyles = createStyles(({ css, cx }, { dimensionsStyles, fontStyles }) => {
+export const useStyles = createStyles(({ css, cx }, { dimensionsStyles, fontStyles }) => {
const shaStatusTag = 'sha-status-tag';
const shaStatusTagContainer = cx(
'sha-status-tag-container',
@@ -12,6 +12,9 @@ export const useStyles = createStyles(({ css, cx }, { dimensionsStyles, fontStyl
> span {
${dimensionsStyles};
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
}
.${shaStatusTag} {
@@ -23,6 +26,9 @@ export const useStyles = createStyles(({ css, cx }, { dimensionsStyles, fontStyl
text-align: center;
align-self: center;
${fontStyles}
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
.sha-help-icon {
diff --git a/shesha-reactjs/src/components/richTextEditor/styles/styles.ts b/shesha-reactjs/src/components/richTextEditor/styles/styles.ts
index 1a23c6a457..10a5032fee 100644
--- a/shesha-reactjs/src/components/richTextEditor/styles/styles.ts
+++ b/shesha-reactjs/src/components/richTextEditor/styles/styles.ts
@@ -1,16 +1,37 @@
-import { createStyles } from '@/styles';
-
-export const useStyles = createStyles(({ css, cx }) => {
- const shaRichTextEditor = cx("sha-rich-text-editor", css`
- background-color: white;
-
- .jodit-status-bar__item-right {
- .jodit-status-bar-link {
- display: none;
- }
- }
- `);
- return {
- shaRichTextEditor,
- };
+import { createStyles } from '@/styles';
+
+export const useStyles = createStyles(({ css, cx }) => {
+ const shaRichTextEditor = cx("sha-rich-text-editor", css`
+ background-color: white;
+ width: 100%;
+ height: 100%;
+ display: flex;
+ flex-direction: column;
+
+ /* Ensure the jodit container fills available space */
+ .jodit-react-container {
+ width: 100% !important;
+ height: 100% !important;
+ }
+
+ .jodit-container {
+ width: 100% !important;
+ height: 100% !important;
+ display: flex;
+ flex-direction: column;
+ }
+
+ .jodit-workplace {
+ flex: 1;
+ }
+
+ .jodit-status-bar__item-right {
+ .jodit-status-bar-link {
+ display: none;
+ }
+ }
+ `);
+ return {
+ shaRichTextEditor,
+ };
});
diff --git a/shesha-reactjs/src/components/sectionSeparator/index.tsx b/shesha-reactjs/src/components/sectionSeparator/index.tsx
index f51c5dafec..81f8f55278 100644
--- a/shesha-reactjs/src/components/sectionSeparator/index.tsx
+++ b/shesha-reactjs/src/components/sectionSeparator/index.tsx
@@ -5,6 +5,7 @@ import { Tooltip } from 'antd';
import { QuestionCircleOutlined } from '@ant-design/icons';
import { addPx } from '@/utils/style';
import { titleDefaultStyles } from './utils';
+import { useAvailableConstantsData } from '@/providers/form/utils';
export interface ISectionSeparatorProps {
id?: string;
@@ -45,6 +46,7 @@ export const SectionSeparator: FC = ({
const { styles } = useStyles();
const titleRef = useRef(null);
const [titleWidth, setTitleWidth] = useState(0);
+ const allData = useAvailableConstantsData();
useEffect(() => {
if (titleRef.current) {
@@ -105,7 +107,7 @@ export const SectionSeparator: FC = ({
return vertical ? (
) : (
@@ -113,7 +115,7 @@ export const SectionSeparator: FC = ({
style={{
...containerStyle,
height: 'max-content',
- width: addPx(lineWidth),
+ width: addPx(lineWidth, allData) ?? '100%',
marginRight: 1,
}}
key={id}
diff --git a/shesha-reactjs/src/components/sidebarContainer/styles/styles.ts b/shesha-reactjs/src/components/sidebarContainer/styles/styles.ts
index d9c7194054..2cc7e3c357 100644
--- a/shesha-reactjs/src/components/sidebarContainer/styles/styles.ts
+++ b/shesha-reactjs/src/components/sidebarContainer/styles/styles.ts
@@ -23,7 +23,7 @@ export const useStyles = createStyles(({ css, cx, prefixCls }) => {
const sidebarContainer = cx("sidebar-container", css`
width: 100%;
- overflow-x: hidden;
+ overflow: hidden;
.${sidebarContainerMainAreaBody}{
overflow: auto;
@@ -162,9 +162,6 @@ export const useStyles = createStyles(({ css, cx, prefixCls }) => {
margin-bottom: 16px;
}
- .${prefixCls}-form-item-label {
- padding-bottom: 4px;
- }
}
.${sidebarContainerMainArea} {
diff --git a/shesha-reactjs/src/components/storedFilesRendererBase/index.tsx b/shesha-reactjs/src/components/storedFilesRendererBase/index.tsx
index 32cd7cb8c1..2ad30460da 100644
--- a/shesha-reactjs/src/components/storedFilesRendererBase/index.tsx
+++ b/shesha-reactjs/src/components/storedFilesRendererBase/index.tsx
@@ -7,6 +7,7 @@ import { IFormComponentStyles } from '@/providers/form/models';
import { IDownloadFilePayload, IReplaceFilePayload, IStoredFile, IUploadFilePayload } from '@/providers/storedFiles/contexts';
import { normalizeFileName } from '@/providers/storedFiles/utils';
import { addPx } from '@/utils/style';
+import { useAvailableConstantsData } from '@/providers/form/utils';
import { DeleteOutlined, DownloadOutlined, FileZipOutlined, PictureOutlined, SyncOutlined, UploadOutlined } from '@ant-design/icons';
import {
Alert,
@@ -136,6 +137,7 @@ export const StoredFilesRendererBase: FC = ({
}) => {
const { message, notification } = App.useApp();
const { httpHeaders } = useSheshaApplication();
+ const allData = useAvailableConstantsData();
const [previewOpen, setPreviewOpen] = useState(false);
const [previewImage, setPreviewImage] = useState<{ url: string; uid: string; name: string } | null>(null);
const [imageUrls, setImageUrls] = useState<{ [key: string]: string }>(fileList.reduce((acc, { uid, url }) => ({ ...acc, [uid]: url }), {}));
@@ -192,8 +194,8 @@ export const StoredFilesRendererBase: FC = ({
downloadedFileStyles: downloadedFileStyles,
containerStyles: {
...(containerDimensionsStyles ?? {}),
- width: layout === 'vertical' && listType === 'thumbnail' ? undefined : addPx(containerDimensionsStyles?.width),
- height: layout === 'horizontal' && listType === 'thumbnail' ? undefined : addPx(containerDimensionsStyles?.height),
+ width: layout === 'vertical' && listType === 'thumbnail' ? undefined : (addPx(containerDimensionsStyles?.width, allData) ?? undefined),
+ height: layout === 'horizontal' && listType === 'thumbnail' ? undefined : (addPx(containerDimensionsStyles?.height, allData) ?? undefined),
...containerJsStyle,
...stylingBoxAsCSS,
},
@@ -201,7 +203,7 @@ export const StoredFilesRendererBase: FC = ({
? { ...(model?.allStyles?.dimensionsStyles ?? {}), ...(model?.allStyles?.fontStyles ?? {}), border: `${defaultBorder.width} ${defaultBorder.style} ${defaultBorder.color}` }
: { ...(model?.allStyles?.fullStyle ?? {}) },
model: {
- gap: addPx(gap),
+ gap: addPx(gap, allData) ?? '0px',
layout: listType === 'thumbnail' && !isDragger,
hideFileName: hideFileName && listType === 'thumbnail',
isDragger,
diff --git a/shesha-reactjs/src/components/storedFilesRendererBase/styles/styles.ts b/shesha-reactjs/src/components/storedFilesRendererBase/styles/styles.ts
index 127928e662..1613f1051e 100644
--- a/shesha-reactjs/src/components/storedFilesRendererBase/styles/styles.ts
+++ b/shesha-reactjs/src/components/storedFilesRendererBase/styles/styles.ts
@@ -161,14 +161,6 @@ export const useStyles = createStyles(({ token, css, cx, prefixCls }, { style =
const shaStoredFilesRenderer = cx("sha-stored-files-renderer", css`
- margin-top: ${marginTop};
- margin-left: ${marginLeft};
- margin-right: ${marginRight};
- margin-bottom: ${marginBottom};
- padding-top: ${paddingTop ?? '2px'};
- padding-left: ${paddingLeft ?? '2px'};
- padding-right: ${paddingRight ?? '2px'};
- padding-bottom: ${paddingBottom ?? '2px'};
height: ${containerHeight ?? 'auto'} !important;
width: ${containerWidth ?? '100%'} !important;
max-height: ${containerMaxHeight ?? 'auto'} !important;
diff --git a/shesha-reactjs/src/components/tablePager/style.ts b/shesha-reactjs/src/components/tablePager/style.ts
index 930cd6bfc1..da1bb40928 100644
--- a/shesha-reactjs/src/components/tablePager/style.ts
+++ b/shesha-reactjs/src/components/tablePager/style.ts
@@ -60,6 +60,8 @@ export const useStyles = createStyles(({ css, cx }, { style }) => {
align-items: center;
flex-wrap: nowrap;
justify-content: center;
+ height: 100%;
+ align-self: center;
`);
return {
diff --git a/shesha-reactjs/src/designer-components/_settings/components/formItem.tsx b/shesha-reactjs/src/designer-components/_settings/components/formItem.tsx
index 5daa76dee0..f30a985f2f 100644
--- a/shesha-reactjs/src/designer-components/_settings/components/formItem.tsx
+++ b/shesha-reactjs/src/designer-components/_settings/components/formItem.tsx
@@ -45,9 +45,7 @@ const FormItem: FC = (props) => {
hidden,
layout,
size: 'small',
-
}}
-
className="sha-js-label"
>
{(value, onChange) =>
diff --git a/shesha-reactjs/src/designer-components/_settings/settingContainer/settingContainerDesigner.tsx b/shesha-reactjs/src/designer-components/_settings/settingContainer/settingContainerDesigner.tsx
index 5b6c941321..4a3d0a3384 100644
--- a/shesha-reactjs/src/designer-components/_settings/settingContainer/settingContainerDesigner.tsx
+++ b/shesha-reactjs/src/designer-components/_settings/settingContainer/settingContainerDesigner.tsx
@@ -77,7 +77,6 @@ export const SettingContainerDesigner: FC = (props) => {
)}
>
<>
- {childIds.length === 0 && Drag and Drop form component
}
= (props) => {
className={noDefaultStyling ? '' : styles.shaComponentsContainerInner}
style={{ ...style, ...incomingStyle }}
>
+ {childIds.length === 0 && Drag and Drop form component
}
{component?.id && }
>
diff --git a/shesha-reactjs/src/designer-components/_settings/styles/styles.ts b/shesha-reactjs/src/designer-components/_settings/styles/styles.ts
index 516df68504..7b38b33af9 100644
--- a/shesha-reactjs/src/designer-components/_settings/styles/styles.ts
+++ b/shesha-reactjs/src/designer-components/_settings/styles/styles.ts
@@ -1,6 +1,9 @@
import { createStyles, sheshaStyles } from '@/styles';
export const useStyles = createStyles(({ css, cx, responsive, token }) => {
+ const formItem = cx(css`
+ margin: 0px !important;
+ `);
const contentJs = cx(css`
position: relative;
top: 0px;
@@ -33,6 +36,7 @@ export const useStyles = createStyles(({ css, cx, responsive, token }) => {
font-weight: 500;
position: relative;
+
+.ant-form-item-tooltip {
align-self: end !important;
position: relative;
@@ -40,9 +44,6 @@ export const useStyles = createStyles(({ css, cx, responsive, token }) => {
}
`);
- const bindingOptionsBtn = cx(css`
- top: -8px;
- `);
const jsSwitch = cx(css`
position: absolute;
right: 0;
@@ -81,7 +82,7 @@ export const useStyles = createStyles(({ css, cx, responsive, token }) => {
contentCode,
jsContent,
label,
- bindingOptionsBtn,
jsSwitch,
+ formItem,
};
});
diff --git a/shesha-reactjs/src/designer-components/_settings/utils/background/utils.tsx b/shesha-reactjs/src/designer-components/_settings/utils/background/utils.tsx
index a0ee87a229..c1a47b4869 100644
--- a/shesha-reactjs/src/designer-components/_settings/utils/background/utils.tsx
+++ b/shesha-reactjs/src/designer-components/_settings/utils/background/utils.tsx
@@ -1,6 +1,6 @@
import React from "react";
import { IBackgroundValue, IDropdownOption, IRadioOption } from "./interfaces";
-import { isDefined, isNullOrWhiteSpace } from "@/utils/nullables";
+import { isDefined } from "@/utils/nullables";
export const getBackgroundImageUrl = async (propertyName: IBackgroundValue | undefined, backendUrl: string, httpHeaders: object): Promise => {
return (
@@ -44,9 +44,16 @@ export const getBackgroundStyle = (input: IBackgroundValue | undefined, jsStyle:
break;
}
case 'gradient': {
+ const direction = input.gradient?.direction;
+ const isRadial = direction === 'radial';
+ const isConic = direction === 'conic';
const colors = input.gradient?.colors || [];
- const colorsString = Object.values(colors).filter((color) => !isNullOrWhiteSpace(color)).join(', ');
- style.backgroundImage = input.gradient?.direction === 'radial' ? `radial-gradient(${colorsString})` : `linear-gradient(${input.gradient?.direction || 'to right'}, ${colorsString})`;
+ const colorsString = Object.values(colors).filter((color) => color && color.trim() !== '').join(', ');
+ if (colorsString) {
+ style.backgroundImage = isRadial || isConic
+ ? `${direction}-gradient(${colorsString})`
+ : `linear-gradient(${direction || 'to right'}, ${colorsString})`;
+ }
break;
}
case 'url': {
@@ -77,6 +84,7 @@ export const gradientDirectionOptions: IDropdownOption[] = [
{ value: 'to bottom right', label: 'To bottom right' },
{ value: 'to bottom left', label: 'To bottom left' },
{ value: 'radial', label: 'Radial' },
+ { value: 'conic', label: 'Conic' },
];
export const backgroundTypeOptions: IRadioOption[] = [
diff --git a/shesha-reactjs/src/designer-components/_settings/utils/border/utils.tsx b/shesha-reactjs/src/designer-components/_settings/utils/border/utils.tsx
index 8af04e9ad8..cab703efa0 100644
--- a/shesha-reactjs/src/designer-components/_settings/utils/border/utils.tsx
+++ b/shesha-reactjs/src/designer-components/_settings/utils/border/utils.tsx
@@ -165,6 +165,7 @@ export const getBorderInputs = (fbf: FormBuilderFactory, path = '', isResponsive
propertyName: '',
icon: 'BorderOutlined',
tooltip: `Styles will apply to all border`,
+ width: 24,
},
{
id: nanoid(),
@@ -214,6 +215,7 @@ export const getBorderInputs = (fbf: FormBuilderFactory, path = '', isResponsive
propertyName: 'bordericon',
icon: sideValue.icon,
tooltip: `Styles will apply to ${side} border`,
+ width: 24,
},
{
id: nanoid(),
@@ -261,8 +263,8 @@ export const getCornerInputs = (fbf: FormBuilderFactory, path = '', isResponsive
inputType: 'radio',
label: 'Radius Type',
propertyName: `${path ? path + '.' : ''}border.radiusType`,
- // defaultValue: 'all',
buttonGroupOptions: radiusConfigType,
+ width: 24,
})
.addSettingsInputRow({
id: nanoid(),
diff --git a/shesha-reactjs/src/designer-components/_settings/utils/dimensions/utils.tsx b/shesha-reactjs/src/designer-components/_settings/utils/dimensions/utils.tsx
index 2786319b22..33d02adfa7 100644
--- a/shesha-reactjs/src/designer-components/_settings/utils/dimensions/utils.tsx
+++ b/shesha-reactjs/src/designer-components/_settings/utils/dimensions/utils.tsx
@@ -3,45 +3,142 @@ import { EyeOutlined, EyeInvisibleOutlined, ColumnWidthOutlined, BorderlessTable
import { IDimensionsValue } from "./interfaces";
import { addPx, hasNumber } from "@/utils/style";
import { IDropdownOption } from "@/designer-components/settingsInput/interfaces";
-import { widthRelativeToCanvas } from "@/providers/canvas/utils";
+import { dimensionRelativeToCanvas } from "@/providers/canvas/utils";
-const getDimension = (main: string | number, left: any, right: any, canvasWidth?: string): string => {
- const value = canvasWidth !== null ? widthRelativeToCanvas(main, canvasWidth) : main;
- return `calc(${addPx(value)} - ${addPx(left || '0')} - ${addPx(right || '0')})`;
+const getWidthDimension = (main: string | number, canvasWidth?: string, context?: object): string | number => {
+ // If canvasWidth is provided and main contains vw, convert to calc
+ if (canvasWidth && typeof main === 'string' && /vw/i.test(main)) {
+ return dimensionRelativeToCanvas(main, canvasWidth, 'vw');
+ }
+
+ // For simple numeric values or values without vw, use addPx
+ if (typeof main === 'string' && /^calc\(/i.test(main.trim())) return main;
+ return !hasNumber(main) ? main : addPx(main, context);
};
-export const getDimensionsStyle = (dimensions: IDimensionsValue | undefined, additionalStyles?: CSSProperties, canvasWidth?: string): CSSProperties => {
+const getHeightDimension = (main: string | number, canvasHeight?: string, context?: object): string | number => {
+ // If canvasHeight is provided and main contains vh, convert to calc
+ if (canvasHeight && typeof main === 'string' && /vh/i.test(main)) {
+ return dimensionRelativeToCanvas(main, canvasHeight, 'vh');
+ }
+
+ // For simple numeric values or values without vh, use addPx
+ if (typeof main === 'string' && /^calc\(/i.test(main.trim())) return main;
+ return !hasNumber(main) ? main : addPx(main, context);
+};
+
+/**
+ * Checks if a value is a calc() expression (e.g., from converted vw/vh units).
+ * @param value - The value to check
+ * @returns true if the value is a calc() expression
+ */
+const isCalcExpression = (value: string | number | undefined): boolean => {
+ if (typeof value !== 'string') return false;
+ return value.trim().toLowerCase().startsWith('calc(');
+};
+
+/**
+ * Internal helper that computes dimension calculation with margins.
+ * Shared logic for getCalculatedDimension and getDesignerCalculatedDimension.
+ *
+ * @param main - The main dimension value
+ * @param firstMargin - First margin value
+ * @param secondMargin - Second margin value
+ * @param defaultMain - Default value to use when main is null/undefined
+ * @param fallbackForAddPx - Fallback value when addPx returns undefined
+ * @param context - Optional context object for executing JS code
+ * @returns A calc() string that subtracts margins from the main dimension
+ */
+const computeDimension = (
+ main: string | number,
+ firstMargin: string | number | undefined,
+ secondMargin: string | number | undefined,
+ defaultMain: string,
+ fallbackForAddPx: string,
+ context?: object,
+): string => {
+ const mainValue = main ?? defaultMain;
+ const margin1 = addPx(firstMargin ?? 0, context);
+ const margin2 = addPx(secondMargin ?? 0, context);
+
+ // For calc() expressions (converted vw/vh), nest the calc to preserve the original calculation
+ if (isCalcExpression(mainValue)) {
+ return `calc(${mainValue} - ${margin1} - ${margin2})`;
+ }
+
+ // For non-numeric values (auto, max-content, min-content, etc.), return as-is
+ // These CSS keywords can't be used in calc() expressions
+ if (!hasNumber(mainValue)) {
+ return typeof mainValue === 'string' ? mainValue : String(mainValue);
+ }
+
+ // For regular numeric values, use the standard calc format
+ return `calc(${addPx(mainValue, context) ?? fallbackForAddPx} - ${margin1} - ${margin2})`;
+};
+
+export const getCalculatedDimension = (main: string | number, firstMargin?: string | number, secondMargin?: string | number, context?: object): string => {
+ return computeDimension(main, firstMargin, secondMargin, 'auto', '0px', context);
+};
+
+/**
+ * Calculates a dimension value adjusted for margins, with special handling for calc() expressions.
+ * This is used in designer mode to account for margins while preserving canvas-relative calculations.
+ *
+ * For regular values: returns `calc(main - margin1 - margin2)`
+ * For calc() expressions (e.g., converted vw/vh): nests the calc to preserve the original calculation
+ *
+ * @param main - The main dimension value (can be a calc() expression from vw/vh conversion)
+ * @param firstMargin - First margin value (e.g., left or top margin)
+ * @param secondMargin - Second margin value (e.g., right or bottom margin)
+ * @param context - Optional context object for executing JS code
+ * @returns A calc() string that subtracts margins from the main dimension
+ *
+ * @example
+ * ```tsx
+ * // Regular value
+ * getDesignerCalculatedDimension('100%', '5px', '5px')
+ * // Returns: 'calc(100% - 5px - 5px)'
+ *
+ * // Converted vw value (canvas-relative)
+ * getDesignerCalculatedDimension('calc((50 * 1024px) / 100)', '5px', '5px')
+ * // Returns: 'calc(calc((50 * 1024px) / 100) - 5px - 5px)'
+ * ```
+ */
+export const getDesignerCalculatedDimension = (
+ main: string | number,
+ firstMargin?: string | number,
+ secondMargin?: string | number,
+ context?: object,
+): string => {
+ return computeDimension(main, firstMargin, secondMargin, '100%', '100%', context);
+};
+
+export const getDimensionsStyle = (
+ dimensions: IDimensionsValue | undefined,
+ canvasWidth?: string,
+ canvasHeight?: string,
+ context?: object,
+): CSSProperties => {
+ const { width, minWidth, maxWidth, height, minHeight, maxHeight } = dimensions || {};
+
return {
- width: dimensions?.width
- ? hasNumber(dimensions.width)
- ? getDimension(dimensions.width, additionalStyles?.marginLeft, additionalStyles?.marginRight, canvasWidth)
- : dimensions.width
- : undefined,
- height: dimensions?.height
- ? hasNumber(dimensions.height)
- ? getDimension(dimensions.height, additionalStyles?.marginTop, additionalStyles?.marginBottom)
- : dimensions.height
+ width: width
+ ? getWidthDimension(width, canvasWidth, context)
: undefined,
- minWidth: dimensions?.minWidth
- ? hasNumber(dimensions.minWidth)
- ? getDimension(dimensions.minWidth, additionalStyles?.marginLeft, additionalStyles?.marginRight, canvasWidth)
- : dimensions.minWidth
+ height: height
+ ? getHeightDimension(height, canvasHeight, context)
: undefined,
- minHeight: dimensions?.minHeight
- ? hasNumber(dimensions.minHeight)
- ? getDimension(dimensions.minHeight, additionalStyles?.marginTop, additionalStyles?.marginBottom)
- : dimensions.minHeight
+ minWidth: minWidth
+ ? getWidthDimension(minWidth, canvasWidth, context)
: undefined,
- maxWidth: dimensions?.maxWidth
- ? hasNumber(dimensions.maxWidth)
- ? getDimension(dimensions.maxWidth, additionalStyles?.marginLeft, additionalStyles?.marginRight, canvasWidth)
- : dimensions.maxWidth
+ minHeight: minHeight
+ ? getHeightDimension(minHeight, canvasHeight, context)
: undefined,
- maxHeight: dimensions?.maxHeight
- ? hasNumber(dimensions.maxHeight)
- ? getDimension(dimensions.maxHeight, additionalStyles?.marginTop, additionalStyles?.marginBottom)
- : dimensions.maxHeight
+ maxWidth: maxWidth
+ ? getWidthDimension(maxWidth, canvasWidth, context)
: undefined,
+ maxHeight: maxHeight
+ ? getHeightDimension(maxHeight, canvasHeight, context) : undefined,
};
};
diff --git a/shesha-reactjs/src/designer-components/address/index.tsx b/shesha-reactjs/src/designer-components/address/index.tsx
index 4cb57e0974..391ee3e669 100644
--- a/shesha-reactjs/src/designer-components/address/index.tsx
+++ b/shesha-reactjs/src/designer-components/address/index.tsx
@@ -19,6 +19,7 @@ const AddressCompoment: IToolboxComponent = {
isInput: true,
isOutput: true,
icon: ,
+ preserveDimensionsInDesigner: true,
Factory: ({ model }) => {
const allData = useAvailableConstantsData();
const customEvents = getEventHandlers(model, allData);
diff --git a/shesha-reactjs/src/designer-components/attachmentsEditor/attachmentsEditor.tsx b/shesha-reactjs/src/designer-components/attachmentsEditor/attachmentsEditor.tsx
index 8f7f913820..a996b9686a 100644
--- a/shesha-reactjs/src/designer-components/attachmentsEditor/attachmentsEditor.tsx
+++ b/shesha-reactjs/src/designer-components/attachmentsEditor/attachmentsEditor.tsx
@@ -246,6 +246,7 @@ const AttachmentsEditor: IToolboxComponent = {
isDynamic={model.isDynamic}
extraFormId={model.extraFormId}
{...model}
+ container={model.container}
enableStyleOnReadonly={model.enableStyleOnReadonly}
ownerId={ownerId}
downloadedFileStyles={model.styleDownloadedFiles ? downloadedFileFullStyle : {}}
diff --git a/shesha-reactjs/src/designer-components/autocomplete/autocomplete.tsx b/shesha-reactjs/src/designer-components/autocomplete/autocomplete.tsx
index bec2a508ba..f152dff2a1 100644
--- a/shesha-reactjs/src/designer-components/autocomplete/autocomplete.tsx
+++ b/shesha-reactjs/src/designer-components/autocomplete/autocomplete.tsx
@@ -34,6 +34,7 @@ const AutocompleteComponent: AutocompleteComponentDefinition = {
canBeJsSetting: true,
name: 'Autocomplete',
icon: ,
+ preserveDimensionsInDesigner: true,
dataTypeSupported: ({ dataType, dataFormat }) =>
dataType === DataTypes.entityReference ||
(dataType === DataTypes.array && [ArrayFormats.entityReference, ArrayFormats.manyToManyEntities].includes(dataFormat)),
diff --git a/shesha-reactjs/src/designer-components/autocompleteTagGroup/index.tsx b/shesha-reactjs/src/designer-components/autocompleteTagGroup/index.tsx
index c03c3df9d1..ae692a7099 100644
--- a/shesha-reactjs/src/designer-components/autocompleteTagGroup/index.tsx
+++ b/shesha-reactjs/src/designer-components/autocompleteTagGroup/index.tsx
@@ -25,6 +25,7 @@ const AutocompleteTagGroupComponent: IToolboxComponent {
return (
diff --git a/shesha-reactjs/src/designer-components/button/button.tsx b/shesha-reactjs/src/designer-components/button/button.tsx
index 14b066f184..e2037841e4 100644
--- a/shesha-reactjs/src/designer-components/button/button.tsx
+++ b/shesha-reactjs/src/designer-components/button/button.tsx
@@ -1,5 +1,5 @@
import ConfigurableButton from './configurableButton';
-import React from 'react';
+import React, { useMemo } from 'react';
import { BorderOutlined } from '@ant-design/icons';
import { getSettings } from './settingsForm';
import { validateConfigurableComponentSettings } from '@/providers/form/utils';
@@ -14,6 +14,8 @@ import { migrateVisibility } from '@/designer-components/_common-migrations/migr
import { migrateFormApi } from '../_common-migrations/migrateFormApi1';
import { migratePrevStyles } from '../_common-migrations/migrateStyles';
import { defaultStyles } from './util';
+import { useShaFormInstance } from '@/providers';
+import { dimensionUtils } from '@/components/formDesigner/utils/dimensionUtils';
export type IActionParameters = [{ key: string; value: string }];
@@ -21,20 +23,56 @@ const ButtonComponent: IToolboxComponent = {
type: 'button',
isInput: false,
name: 'Button',
+ /**
+ * Custom dimension calculation for designer mode.
+ * - Buttons with 'auto' width -> wrapper uses 'max-content' (shrinks to fit), button fills 100%
+ * - Buttons with absolute/relative width -> wrapper gets that width, button fills 100%
+ */
+ getDesignerDimensions: (originalDims, deviceDims) => {
+ const isAutoWidth = originalDims?.width === 'auto';
+ if (isAutoWidth) {
+ // WYSIWYG: Wrapper shrinks to fit content, button fills wrapper
+ return { ...deviceDims, width: 'max-content' };
+ }
+
+ // Default: fill the wrapper
+ return deviceDims;
+ },
icon: ,
Factory: ({ model, form }) => {
+ const shaForm = useShaFormInstance();
const { style, ...restProps } = model;
- const finalStyle = {
- ...model.allStyles.dimensionsStyles,
- ...(['primary', 'default'].includes(model.buttonType) && !model.readOnly && model.allStyles.borderStyles),
- ...model.allStyles.fontStyles,
- ...(['dashed', 'default'].includes(model.buttonType) && !model.readOnly && model.allStyles.backgroundStyles),
- ...(['primary', 'default'].includes(model.buttonType) && model.allStyles.shadowStyles),
- ...model.allStyles.stylingBoxAsCSS,
- ...model.allStyles.jsStyle,
- justifyContent: model.font?.align,
- };
+ const isDesignerMode = shaForm?.formMode === 'designer';
+
+ // Merge base styles with designer dimensions
+ // Button preserves its original dimensions in designer mode
+ const finalStyle = useMemo(() => dimensionUtils.mergeWithDesignerDimensions(
+ {
+ ...model.allStyles?.dimensionsStyles,
+ ...(['primary', 'default'].includes(model.buttonType) && !model.readOnly && model.allStyles?.borderStyles),
+ ...model.allStyles?.fontStyles,
+ ...(['dashed', 'default'].includes(model.buttonType) && !model.readOnly && model.allStyles?.backgroundStyles),
+ ...(['primary', 'default'].includes(model.buttonType) && model.allStyles?.shadowStyles),
+ ...model.allStyles?.stylingBoxAsCSS,
+ ...model.allStyles?.jsStyle,
+ justifyContent: model.font?.align,
+ },
+ isDesignerMode,
+ true, // Preserve original dimensions in designer mode
+ ), [
+ model.allStyles?.dimensionsStyles,
+ model.allStyles?.borderStyles,
+ model.allStyles?.fontStyles,
+ model.allStyles?.backgroundStyles,
+ model.allStyles?.shadowStyles,
+ model.allStyles?.stylingBoxAsCSS,
+ model.allStyles?.jsStyle,
+ model.buttonType,
+ model.readOnly,
+ model.font?.align,
+ isDesignerMode,
+ ]);
return model.hidden ? null : (
,
props.id,
@@ -161,7 +161,7 @@ const InlineItem: FC = (props) => {
return ;
case 'separator':
case 'line':
- return ;
+ return ;
default:
return null;
}
diff --git a/shesha-reactjs/src/designer-components/button/buttonGroup/buttonGroupComponent.tsx b/shesha-reactjs/src/designer-components/button/buttonGroup/buttonGroupComponent.tsx
index 70c8d6fd0e..25b8512570 100644
--- a/shesha-reactjs/src/designer-components/button/buttonGroup/buttonGroupComponent.tsx
+++ b/shesha-reactjs/src/designer-components/button/buttonGroup/buttonGroupComponent.tsx
@@ -23,6 +23,8 @@ const ButtonGroupComponent: IToolboxComponent = {
isInput: false,
name: 'Button Group',
icon: ,
+ // Button Group preserves its original dimensions in designer mode (like image component)
+ preserveDimensionsInDesigner: true,
Factory: ({ model, form }) => {
const { styles } = useStyles();
return model.hidden ? null
diff --git a/shesha-reactjs/src/designer-components/calendar/index.tsx b/shesha-reactjs/src/designer-components/calendar/index.tsx
index f24da3fa97..6bd9e7a34a 100644
--- a/shesha-reactjs/src/designer-components/calendar/index.tsx
+++ b/shesha-reactjs/src/designer-components/calendar/index.tsx
@@ -14,6 +14,7 @@ const CalendarComponent: IToolboxComponent = {
type: 'calendar',
isInput: true,
name: 'Calendar',
+ preserveDimensionsInDesigner: true,
icon: ,
Factory: ({ model }) => {
const { allStyles } = model;
diff --git a/shesha-reactjs/src/designer-components/card/index.tsx b/shesha-reactjs/src/designer-components/card/index.tsx
index 50bb568e6a..01eb24a77e 100644
--- a/shesha-reactjs/src/designer-components/card/index.tsx
+++ b/shesha-reactjs/src/designer-components/card/index.tsx
@@ -26,6 +26,7 @@ const CardComponent: IToolboxComponent = {
type: 'card',
isInput: false,
name: 'Card',
+ preserveDimensionsInDesigner: true,
icon: ,
Factory: ({ model }) => {
const { data } = useFormData();
@@ -89,6 +90,7 @@ const CardComponent: IToolboxComponent = {
className={classNames(model.className, { [styles.hideWhenEmpty]: model.hideWhenEmpty })}
title={title}
extra={extra}
+ styles={{}}
style={{ ...removeNullUndefined(newStyles), ...getLayoutStyle(model, { data, globalState }) }}
>
= {
name: 'Bar Chart',
isInput: false,
isOutput: true,
+ preserveDimensionsInDesigner: ["height"],
icon: ,
Factory: ({ model }) => {
useShaFormDataUpdate();
@@ -66,7 +67,7 @@ const BarChartComponent: IToolboxComponent = {
// Don't render chart until filters are ready to prevent race conditions
if (!filtersReady) {
return (
-
+
Fetching data...
@@ -76,7 +77,7 @@ const BarChartComponent: IToolboxComponent
= {
}
return (
-
+
{() => {
return (
diff --git a/shesha-reactjs/src/designer-components/charts/line.tsx b/shesha-reactjs/src/designer-components/charts/line.tsx
index 9847c283b0..9a63e425a1 100644
--- a/shesha-reactjs/src/designer-components/charts/line.tsx
+++ b/shesha-reactjs/src/designer-components/charts/line.tsx
@@ -22,6 +22,7 @@ const LineChartComponent: IToolboxComponent = {
name: 'Line Chart',
isInput: false,
isOutput: true,
+ preserveDimensionsInDesigner: ["height"],
icon: ,
Factory: ({ model }) => {
useShaFormDataUpdate();
@@ -51,7 +52,7 @@ const LineChartComponent: IToolboxComponent = {
// Show error alert if there was an error evaluating filters
if (filterError) {
return (
-
+
= {
// Don't render chart until filters are ready to prevent race conditions
if (!filtersReady) {
return (
-
+
Fetching data...
@@ -76,7 +77,7 @@ const LineChartComponent: IToolboxComponent
= {
}
return (
-
+
{() => {
return (
diff --git a/shesha-reactjs/src/designer-components/charts/pie.tsx b/shesha-reactjs/src/designer-components/charts/pie.tsx
index 464eb2b942..3bf2a4ef21 100644
--- a/shesha-reactjs/src/designer-components/charts/pie.tsx
+++ b/shesha-reactjs/src/designer-components/charts/pie.tsx
@@ -22,6 +22,7 @@ const PieChartComponent: IToolboxComponent = {
name: 'Pie Chart',
isInput: false,
isOutput: true,
+ preserveDimensionsInDesigner: ["height"],
icon: ,
Factory: ({ model }) => {
useShaFormDataUpdate();
@@ -51,7 +52,7 @@ const PieChartComponent: IToolboxComponent = {
// Show error alert if there was an error evaluating filters
if (filterError) {
return (
-
+
= {
// Don't render chart until filters are ready to prevent race conditions
if (!filtersReady) {
return (
-
+
Fetching data...
@@ -76,7 +77,7 @@ const PieChartComponent: IToolboxComponent
= {
}
return (
-
+
{() => {
return (
diff --git a/shesha-reactjs/src/designer-components/charts/polarArea.tsx b/shesha-reactjs/src/designer-components/charts/polarArea.tsx
index 4b3a6067f8..6ec6fd33b8 100644
--- a/shesha-reactjs/src/designer-components/charts/polarArea.tsx
+++ b/shesha-reactjs/src/designer-components/charts/polarArea.tsx
@@ -51,7 +51,7 @@ const PolarAreaChartComponent: IToolboxComponent = {
// Show error alert if there was an error evaluating filters
if (filterError) {
return (
-
+
= {
// Don't render chart until filters are ready to prevent race conditions
if (!filtersReady) {
return (
-
+
Fetching data...
@@ -76,7 +76,7 @@ const PolarAreaChartComponent: IToolboxComponent
= {
}
return (
-
+
{() => {
return (
diff --git a/shesha-reactjs/src/designer-components/charts/styles.ts b/shesha-reactjs/src/designer-components/charts/styles.ts
index 1660e6ddb1..20f72da606 100644
--- a/shesha-reactjs/src/designer-components/charts/styles.ts
+++ b/shesha-reactjs/src/designer-components/charts/styles.ts
@@ -330,6 +330,16 @@ const useStyles = createStyles(({ css, cx, prefixCls }) => {
overflow: hidden;
`);
+ const formItem = cx(css`
+ &.ant-form-item,
+ & .ant-form-item-row,
+ & .ant-form-item-control,
+ & .ant-form-item-control-input,
+ & .ant-form-item-control-input-content {
+ height: 100%;
+ }
+ `);
+
return {
responsiveChartContainer,
chartContainerWithBorder,
@@ -346,6 +356,8 @@ const useStyles = createStyles(({ css, cx, prefixCls }) => {
line,
dot,
segment,
+
+ formItem,
};
});
diff --git a/shesha-reactjs/src/designer-components/checkbox/checkbox.tsx b/shesha-reactjs/src/designer-components/checkbox/checkbox.tsx
index dcb07675b3..2027295793 100644
--- a/shesha-reactjs/src/designer-components/checkbox/checkbox.tsx
+++ b/shesha-reactjs/src/designer-components/checkbox/checkbox.tsx
@@ -33,6 +33,11 @@ const CheckboxComponent: CheckboxComponentDefinition = {
canBeJsSetting: true,
name: 'Checkbox',
icon: ,
+ /**
+ * Checkbox dimensions apply to the checkbox element itself via CSS,
+ * not to the Form.Item wrapper. The wrapper should fill normally.
+ */
+ preserveDimensionsInDesigner: true,
dataTypeSupported: ({ dataType }) => dataType === DataTypes.boolean,
calculateModel: (model, allData) => ({ eventHandlers: getAllEventHandlers(model, allData) }),
Factory: ({ model, calculatedModel }) => {
@@ -75,7 +80,7 @@ const CheckboxComponent: CheckboxComponentDefinition = {
return { ...prev, desktop: { ...styles }, tablet: { ...styles }, mobile: { ...styles } };
})
- .add(5, (prev) => (migratePrevStyles(prev, defaultStyles()))),
+ .add(5, (prev) => (migratePrevStyles(prev, defaultStyles(prev)))),
};
export default CheckboxComponent;
diff --git a/shesha-reactjs/src/designer-components/checkbox/settingsForm.ts b/shesha-reactjs/src/designer-components/checkbox/settingsForm.ts
index c90d561112..e14384a6f0 100644
--- a/shesha-reactjs/src/designer-components/checkbox/settingsForm.ts
+++ b/shesha-reactjs/src/designer-components/checkbox/settingsForm.ts
@@ -181,7 +181,6 @@ export const getSettings: SettingsFormMarkupFactory = ({ fbf }) => {
parentId: styleRouterId,
ghost: true,
collapsible: 'header',
- hidden: { _code: 'return getSettingValue(data[`${contexts.canvasContext?.designerDevice || "desktop"}`]?.displayStyle) === "tags" && getSettingValue(data.mode) === "single";', _mode: 'code', _value: false } as any,
content: {
id: nanoid(),
components: [...fbf()
@@ -196,7 +195,6 @@ export const getSettings: SettingsFormMarkupFactory = ({ fbf }) => {
id: nanoid(),
label: 'Size',
propertyName: 'font.size',
- hideLabel: true,
width: 50,
},
{
@@ -236,7 +234,7 @@ export const getSettings: SettingsFormMarkupFactory = ({ fbf }) => {
components: [...fbf()
.addSettingsInputRow({
id: nanoid(),
- parentId: nanoid(),
+ parentId: styleRouterId,
inline: true,
label: 'Width',
inputs: [
@@ -269,38 +267,39 @@ export const getSettings: SettingsFormMarkupFactory = ({ fbf }) => {
icon: "maxWidthIcon",
},
],
+
})
.addSettingsInputRow({
id: nanoid(),
- parentId: nanoid(),
+ parentId: styleRouterId,
inline: true,
inputs: [
{
type: 'textField',
id: nanoid(),
- label: "Height",
+ label: 'Height',
width: 85,
- propertyName: "dimensions.height",
- icon: "heightIcon",
- tooltip: "You can use any unit (%, px, em, etc). px by default if without unit",
+ propertyName: 'dimensions.height',
+ icon: 'heightIcon',
+ tooltip: 'You can use any unit (%, px, em, etc). px by default if without unit',
},
{
type: 'textField',
id: nanoid(),
- label: "Min Height",
+ label: 'Min Height',
width: 85,
hideLabel: true,
- propertyName: "dimensions.minHeight",
- icon: "minHeightIcon",
+ propertyName: 'dimensions.minHeight',
+ icon: 'minHeightIcon',
},
{
type: 'textField',
id: nanoid(),
- label: "Max Height",
+ label: 'Max Height',
width: 85,
hideLabel: true,
- propertyName: "dimensions.maxHeight",
- icon: "maxHeightIcon",
+ propertyName: 'dimensions.maxHeight',
+ icon: 'maxHeightIcon',
},
],
})
diff --git a/shesha-reactjs/src/designer-components/checkbox/styles.ts b/shesha-reactjs/src/designer-components/checkbox/styles.ts
index fcd8ddc6f0..74ab9d53f7 100644
--- a/shesha-reactjs/src/designer-components/checkbox/styles.ts
+++ b/shesha-reactjs/src/designer-components/checkbox/styles.ts
@@ -22,7 +22,14 @@ export const useStyles = createStyles(({ css, cx, prefixCls }, { style }: { styl
};
const checkbox = cx("sha-checkbox", css`
+ .${prefixCls}-checkbox-wrapper {
+ height: 100%;
+ }
.${prefixCls}-checkbox {
+ input {
+ width: 100%;
+ height: 100%;
+ }
.${prefixCls}-checkbox-inner {
--ant-control-interactive-size: ${style?.fontSize};
--ant-line-width-bold: ${borderWidthFromWeight(style?.fontWeight)} !important;
@@ -36,9 +43,6 @@ export const useStyles = createStyles(({ css, cx, prefixCls }, { style }: { styl
:after {
inset-inline-start: unset;
}
- .${prefixCls}-checkbox {
-
- }
}
}
@@ -46,10 +50,6 @@ export const useStyles = createStyles(({ css, cx, prefixCls }, { style }: { styl
.${prefixCls}-checkbox-inner {
background: ${backgroundImage || backgroundColor};
${rest}
-
- .${prefixCls}-checkbox {
-
- }
}
}
`);
diff --git a/shesha-reactjs/src/designer-components/checkbox/utils.ts b/shesha-reactjs/src/designer-components/checkbox/utils.ts
index 5ac017b948..cf766aac13 100644
--- a/shesha-reactjs/src/designer-components/checkbox/utils.ts
+++ b/shesha-reactjs/src/designer-components/checkbox/utils.ts
@@ -1,33 +1,44 @@
-import { IStyleType } from '@/index';
+import { IInputStyles, IStyleType } from '@/index';
+import { ICheckboxComponentProps } from './interfaces';
-export const defaultStyles = (): IStyleType => {
+export const defaultStyles = (prev: ICheckboxComponentProps & IInputStyles): IStyleType => {
return {
- background: { type: 'color', color: '' },
- font: {
- weight: '400',
- size: 14,
- color: '',
- },
border: {
- border: {
- all: {
- width: 1,
- style: 'solid',
- color: '#d9d9d9',
- },
- },
- radius: { all: 4 },
- borderType: 'all',
radiusType: 'all',
+ borderType: 'all',
+ border: { all: { width: '1px', style: 'solid', color: '#d9d9d9' } },
+ radius: { all: 4 },
},
dimensions: {
- width: '16px',
- height: '16px',
+ width: !prev.width || prev.width === 'auto' ? '14px' : prev.width,
+ height: !prev.height || prev.height === 'auto' ? '14px' : prev.height,
minHeight: '0px',
maxHeight: 'auto',
minWidth: '0px',
maxWidth: 'auto',
-
+ },
+ background: {
+ type: 'color',
+ color: '',
+ repeat: 'no-repeat',
+ size: 'cover',
+ position: 'center',
+ gradient: { direction: 'to right', colors: {} },
+ url: '',
+ storedFile: { id: null },
+ uploadFile: null,
+ },
+ font: {
+ color: '',
+ size: 14,
+ weight: '400',
+ },
+ shadow: {
+ offsetX: 0,
+ offsetY: 0,
+ color: '#000',
+ blurRadius: 0,
+ spreadRadius: 0,
},
};
};
diff --git a/shesha-reactjs/src/designer-components/checkboxGroup/checkboxGroup.tsx b/shesha-reactjs/src/designer-components/checkboxGroup/checkboxGroup.tsx
index 89e599e8d2..2e6d43f894 100644
--- a/shesha-reactjs/src/designer-components/checkboxGroup/checkboxGroup.tsx
+++ b/shesha-reactjs/src/designer-components/checkboxGroup/checkboxGroup.tsx
@@ -32,6 +32,8 @@ const CheckboxGroupComponent: IToolboxComponent,
dataTypeSupported: ({ dataType }) => dataType === DataTypes.referenceListItem,
calculateModel: (model, allData) => ({
diff --git a/shesha-reactjs/src/designer-components/chevron/chevron.tsx b/shesha-reactjs/src/designer-components/chevron/chevron.tsx
index 4b80f493c1..91ad8105fd 100644
--- a/shesha-reactjs/src/designer-components/chevron/chevron.tsx
+++ b/shesha-reactjs/src/designer-components/chevron/chevron.tsx
@@ -14,6 +14,7 @@ const ChevronComponent: IToolboxComponent = {
type: 'chevron',
isInput: true,
name: 'Chevron',
+ preserveDimensionsInDesigner: true,
icon: ,
Factory: ({ model }) => {
if (model.hidden) return null;
diff --git a/shesha-reactjs/src/designer-components/codeEditor/index.tsx b/shesha-reactjs/src/designer-components/codeEditor/index.tsx
index f50c87e32c..a4ff086d37 100644
--- a/shesha-reactjs/src/designer-components/codeEditor/index.tsx
+++ b/shesha-reactjs/src/designer-components/codeEditor/index.tsx
@@ -22,6 +22,7 @@ const CodeEditorComponent: CodeEditorComponentDefinition = {
icon: ,
isInput: true,
isOutput: true,
+ preserveDimensionsInDesigner: true,
dataTypeSupported: ({ dataType, dataFormat }) =>
dataType === DataTypes.string && (dataFormat === StringFormats.javascript || dataFormat === StringFormats.json),
Factory: ({ model }) => {
diff --git a/shesha-reactjs/src/designer-components/collapsiblePanel/collapsiblePanelComponent.tsx b/shesha-reactjs/src/designer-components/collapsiblePanel/collapsiblePanelComponent.tsx
index 3a0e3f6187..92aaa8f08d 100644
--- a/shesha-reactjs/src/designer-components/collapsiblePanel/collapsiblePanelComponent.tsx
+++ b/shesha-reactjs/src/designer-components/collapsiblePanel/collapsiblePanelComponent.tsx
@@ -25,7 +25,6 @@ const CollapsiblePanelComponent: CollapsiblePanelComponentDefinition = {
Factory: ({ model }) => {
const { formMode } = useForm();
const { data } = useFormData();
-
const {
label,
expandIconPosition,
@@ -44,7 +43,6 @@ const CollapsiblePanelComponent: CollapsiblePanelComponentDefinition = {
hidden,
} = model;
-
const evaluatedLabel = useMemo(() => (
typeof label === 'string' ? evaluateString(label, data) : label
), [label, data]);
@@ -77,8 +75,8 @@ const CollapsiblePanelComponent: CollapsiblePanelComponentDefinition = {
extra={extra}
collapsible={collapsible === 'header' ? 'header' : 'icon'}
ghost={ghost}
- bodyStyle={model.allStyles.fullStyle}
- headerStyle={headerStyles}
+ bodyStyle={{ ...model.allStyles.fullStyle }}
+ headerStyle={{ ...headerStyles, width: '100%' }}
className={className}
bodyColor={bodyColor}
isSimpleDesign={isSimpleDesign}
diff --git a/shesha-reactjs/src/designer-components/collapsiblePanel/utils.ts b/shesha-reactjs/src/designer-components/collapsiblePanel/utils.ts
index 4eb1d53997..5d71c12380 100644
--- a/shesha-reactjs/src/designer-components/collapsiblePanel/utils.ts
+++ b/shesha-reactjs/src/designer-components/collapsiblePanel/utils.ts
@@ -23,7 +23,7 @@ export const defaultStyles = (prev: ICollapsiblePanelComponentProps): IStyleType
radius: { all: borderRadius || 8 },
},
shadow: { blurRadius: 0, color: 'rgba(0, 0, 0, 0.15)', offsetX: 0, offsetY: 0, spreadRadius: 0 },
- stylingBox: '{"marginBottom":"5","paddingLeft":"8","paddingBottom":"8","paddingTop":"8","paddingRight":"8"}',
+ stylingBox: '{"paddingLeft":"8","paddingBottom":"8","paddingTop":"8","paddingRight":"8","marginLeft":"0","marginBottom":"5","marginTop":"0","marginRight":"0"}',
};
};
@@ -49,7 +49,7 @@ export const defaultHeaderStyles = (prev: ICollapsiblePanelComponentProps): ISty
},
radius: { all: borderRadius || 8 },
},
- stylingBox: '{"paddingLeft":"8","paddingBottom":"8","paddingTop":"8","paddingRight":"8"}',
+ stylingBox: '{"paddingLeft":"8","paddingBottom":"8","paddingTop":"8","paddingRight":"8","marginLeft":"0","marginBottom":"0","marginTop":"0","marginRight":"0"}',
};
};
diff --git a/shesha-reactjs/src/designer-components/colorPicker/index.tsx b/shesha-reactjs/src/designer-components/colorPicker/index.tsx
index d8eeacd4dc..67cbf76f05 100644
--- a/shesha-reactjs/src/designer-components/colorPicker/index.tsx
+++ b/shesha-reactjs/src/designer-components/colorPicker/index.tsx
@@ -17,6 +17,7 @@ const ColorPickerComponent: ColorPickerComponentDefinition = {
isInput: true,
isOutput: true,
icon: ,
+ preserveDimensionsInDesigner: true,
calculateModel: (model, allData) => ({
eventHandlers: getAllEventHandlers(model, allData),
}),
diff --git a/shesha-reactjs/src/designer-components/configurableItemAutocomplete/index.tsx b/shesha-reactjs/src/designer-components/configurableItemAutocomplete/index.tsx
index b1f3b89c50..88f740a3ab 100644
--- a/shesha-reactjs/src/designer-components/configurableItemAutocomplete/index.tsx
+++ b/shesha-reactjs/src/designer-components/configurableItemAutocomplete/index.tsx
@@ -1,88 +1,89 @@
-import { IToolboxComponent } from '@/interfaces';
-import { FormMarkup } from '@/providers/form/models';
-import { FileSearchOutlined } from '@ant-design/icons';
-import ConfigurableFormItem from '@/components/formDesigner/components/formItem';
-import settingsFormJson from './settingsForm.json';
-import React from 'react';
-import { useAvailableConstantsData, validateConfigurableComponentSettings } from '@/providers/form/utils';
-import { IConfigurableItemAutocompleteComponentProps } from './interfaces';
-import { useAsyncDeepCompareMemo } from '@/hooks/useAsyncMemo';
-import { evaluateDynamicFilters } from '@/utils/datatable';
-import { useNestedPropertyMetadatAccessor } from '@/providers';
-import { ConfigItemAutocomplete } from '@/components/configurableItemAutocomplete';
-
-const settingsForm = settingsFormJson as FormMarkup;
-
-export const ConfigurableItemAutocompleteComponent: IToolboxComponent = {
- type: 'configurableItemAutocomplete',
- name: 'Configurable Item Autocomplete',
- icon: ,
- isInput: true,
- isOutput: true,
- canBeJsSetting: true,
- Factory: ({ model }) => {
- const { filter } = model;
- const allData = useAvailableConstantsData();
-
- const propertyMetadataAccessor = useNestedPropertyMetadatAccessor(model.entityType);
- const evaluatedFilter = useAsyncDeepCompareMemo
);
diff --git a/shesha-reactjs/src/designer-components/dropdown/index.tsx b/shesha-reactjs/src/designer-components/dropdown/index.tsx
index 634aca0245..393b910907 100644
--- a/shesha-reactjs/src/designer-components/dropdown/index.tsx
+++ b/shesha-reactjs/src/designer-components/dropdown/index.tsx
@@ -1,171 +1,172 @@
-import ConfigurableFormItem from '@/components/formDesigner/components/formItem';
-import React from 'react';
-import { customDropDownEventHandler } from '@/components/formDesigner/components/utils';
-import { ArrayFormats, DataTypes } from '@/interfaces/dataTypes';
-import { DownSquareOutlined } from '@ant-design/icons';
-import { IInputStyles } from '@/providers/form/models';
-import { getLegacyReferenceListIdentifier } from '@/utils/referenceList';
-import { evaluateString, validateConfigurableComponentSettings } from '@/providers/form/utils';
-import { DropdownComponentDefinition, IDropdownComponentProps } from './model';
-import { migrateCustomFunctions, migratePropertyName, migrateReadOnly } from '@/designer-components/_common-migrations/migrateSettings';
-import { migrateVisibility } from '@/designer-components/_common-migrations/migrateVisibility';
-import { Dropdown } from '@/components/dropdown/dropdown';
-import { migrateFormApi } from '../_common-migrations/migrateFormApi1';
-import { getSettings } from './settingsForm';
-import { migratePrevStyles, migrateStyles } from '../_common-migrations/migrateStyles';
-import { defaultStyles, defaultTagStyles } from './utils';
-import { useFormComponentStyles } from '@/hooks/formComponentHooks';
-
-const DropdownComponent: DropdownComponentDefinition = {
- type: 'dropdown',
- isInput: true,
- isOutput: true,
- canBeJsSetting: true,
- isHidden: false,
- name: 'Dropdown',
- icon: ,
- dataTypeSupported: ({ dataType, dataFormat }) => dataType === DataTypes.referenceListItem || (dataType === DataTypes.array && dataFormat === ArrayFormats.multivalueReferenceList),
- calculateModel: (model, allData) => ({
- eventHandlers: customDropDownEventHandler(model, allData),
- // quick fix not to default to empty string or null while working with multi-mode
- defaultValue: Array.isArray(model.defaultValue)
- ? model.defaultValue
- : model.defaultValue
- ? evaluateString(model.defaultValue, { formData: allData.data, formMode: allData.form.formMode, globalState: allData.globalState }) || undefined
- : undefined,
- }),
- Factory: ({ model, calculatedModel }) => {
- const initialValue = model?.defaultValue ? { initialValue: model.defaultValue } : {};
- const tagStyle = useFormComponentStyles({ ...model.tag }).fullStyle;
-
- // When enableStyleOnReadonly is true, apply all configured styles in readonly mode
- // When enableStyleOnReadonly is false, apply only minimal styles (font + dimensions)
- const finalStyle = model.readOnly
- ? model.enableStyleOnReadonly
- ? { ...model.allStyles.fullStyle, overflow: 'auto' }
- : { ...model.allStyles.fontStyles, ...model.allStyles.dimensionsStyles }
- : { ...model.allStyles.fullStyle, overflow: 'auto' };
-
- return (
-
- {(value, onChange) => {
- const customEvent = calculatedModel.eventHandlers;
- const onChangeInternal = (...args: any[]): void => {
- customEvent.onChange(args[0], args[1]);
- if (typeof onChange === 'function')
- onChange(...args);
- };
-
- return (
-
- );
- }}
-
- );
- },
- settingsFormMarkup: getSettings,
- validateSettings: (model) => validateConfigurableComponentSettings(getSettings, model),
- migrator: (m) => m
- .add(0, (prev) => ({
- ...prev,
- dataSourceType: prev['dataSourceType'] ?? 'values',
- useRawValues: prev['useRawValues'] ?? false,
- }))
- .add(1, (prev) => {
- return {
- ...prev,
- referenceListId: getLegacyReferenceListIdentifier(prev.referenceListNamespace, prev.referenceListName),
- };
- })
- .add(2, (prev) => migratePropertyName(migrateCustomFunctions(prev)))
- .add(3, (prev) => migrateVisibility(prev))
- .add(4, (prev) => migrateReadOnly(prev))
- .add(5, (prev, context) => ({
- ...prev,
- valueFormat: prev.valueFormat ??
- context.isNew
- ? 'simple'
- : prev['useRawValue'] === true
- ? 'simple'
- : 'listItem',
- editMode: prev?.editMode ?? 'inherited',
- }))
- .add(6, (prev) => ({ ...migrateFormApi.eventsAndProperties(prev) }))
- .add(7, (prev) => {
- const styles: IInputStyles = {
- size: prev.size,
- stylingBox: prev.stylingBox,
- style: prev.style,
- };
-
- return { ...prev, desktop: { ...prev.desktop, ...styles }, tablet: { ...prev.tablet, ...styles }, mobile: { ...prev.mobile, ...styles } };
- })
- .add(8, (prev) => {
- const styles: IInputStyles = {
- size: prev.size,
- width: prev.width,
- height: prev.height,
- hideBorder: prev.hideBorder,
- borderSize: prev.borderSize,
- borderRadius: prev.borderRadius,
- borderColor: prev.borderColor,
- fontSize: prev.fontSize,
- fontColor: prev.fontColor,
- backgroundColor: prev.backgroundColor,
- stylingBox: prev.stylingBox,
- };
- return { ...prev, desktop: { ...prev.desktop, ...styles }, tablet: { ...prev.tablet, ...styles }, mobile: { ...prev.mobile, ...styles } };
- })
- .add(9, (prev) => ({ ...migratePrevStyles(prev, defaultStyles()) }))
- .add(10, (prev) => {
- const initTagStyle = migrateStyles({}, defaultTagStyles());
-
- return {
- ...prev,
- tag: { ...initTagStyle },
- showItemName: prev.showItemName ?? true,
- showIcon: prev.showIcon ?? true,
- solidColor: prev.solidColor ?? true,
- displayStyle: prev.displayStyle ?? 'text',
- desktop: { ...prev.desktop, tag: { ...initTagStyle } },
- tablet: { ...prev.tablet, tag: { ...initTagStyle } },
- mobile: { ...prev.mobile, tag: { ...initTagStyle } },
- };
- })
- .add(11, (prev) => {
- const result = { ...prev };
- delete result['referenceListNamespace'];
- delete result['referenceListName'];
- const { referenceListId } = result;
- const knownPrefixes = ["Shesha.Framework", "Shesha.Core", "Shesha.Scheduler"];
- if (referenceListId && referenceListId.name && !referenceListId.module && knownPrefixes.some((p) => referenceListId.name.startsWith(p)))
- result.referenceListId = { module: "Shesha", name: referenceListId.name };
- return result;
- }),
- linkToModelMetadata: (model, metadata): IDropdownComponentProps => {
- const isSingleRefList = metadata.dataType === DataTypes.referenceListItem;
- const isMultipleRefList = metadata.dataType === DataTypes.array && metadata.dataFormat === ArrayFormats.multivalueReferenceList;
-
- return {
- ...model,
- dataSourceType: isSingleRefList || isMultipleRefList ? 'referenceList' : 'values',
- referenceListId: {
- module: metadata.referenceListModule,
- name: metadata.referenceListName,
- },
- mode: isMultipleRefList ? 'multiple' : 'single',
- valueFormat: 'simple',
- };
- },
-};
-
-export default DropdownComponent;
+import ConfigurableFormItem from '@/components/formDesigner/components/formItem';
+import React from 'react';
+import { customDropDownEventHandler } from '@/components/formDesigner/components/utils';
+import { ArrayFormats, DataTypes } from '@/interfaces/dataTypes';
+import { DownSquareOutlined } from '@ant-design/icons';
+import { IInputStyles } from '@/providers/form/models';
+import { getLegacyReferenceListIdentifier } from '@/utils/referenceList';
+import { evaluateString, validateConfigurableComponentSettings } from '@/providers/form/utils';
+import { DropdownComponentDefinition, IDropdownComponentProps } from './model';
+import { migrateCustomFunctions, migratePropertyName, migrateReadOnly } from '@/designer-components/_common-migrations/migrateSettings';
+import { migrateVisibility } from '@/designer-components/_common-migrations/migrateVisibility';
+import { Dropdown } from '@/components/dropdown/dropdown';
+import { migrateFormApi } from '../_common-migrations/migrateFormApi1';
+import { getSettings } from './settingsForm';
+import { migratePrevStyles, migrateStyles } from '../_common-migrations/migrateStyles';
+import { defaultStyles, defaultTagStyles } from './utils';
+import { useFormComponentStyles } from '@/hooks/formComponentHooks';
+
+const DropdownComponent: DropdownComponentDefinition = {
+ type: 'dropdown',
+ isInput: true,
+ isOutput: true,
+ canBeJsSetting: true,
+ isHidden: false,
+ name: 'Dropdown',
+ icon: ,
+ preserveDimensionsInDesigner: true,
+ dataTypeSupported: ({ dataType, dataFormat }) => dataType === DataTypes.referenceListItem || (dataType === DataTypes.array && dataFormat === ArrayFormats.multivalueReferenceList),
+ calculateModel: (model, allData) => ({
+ eventHandlers: customDropDownEventHandler(model, allData),
+ // quick fix not to default to empty string or null while working with multi-mode
+ defaultValue: Array.isArray(model.defaultValue)
+ ? model.defaultValue
+ : model.defaultValue
+ ? evaluateString(model.defaultValue, { formData: allData.data, formMode: allData.form.formMode, globalState: allData.globalState }) || undefined
+ : undefined,
+ }),
+ Factory: ({ model, calculatedModel }) => {
+ const initialValue = model?.defaultValue ? { initialValue: model.defaultValue } : {};
+ const tagStyle = useFormComponentStyles({ ...model.tag }).fullStyle;
+
+ // When enableStyleOnReadonly is true, apply all configured styles in readonly mode
+ // When enableStyleOnReadonly is false, apply only minimal styles (font + dimensions)
+ const finalStyle = model.readOnly
+ ? model.enableStyleOnReadonly
+ ? { ...model.allStyles.fullStyle, overflow: 'auto' }
+ : { ...model.allStyles.fontStyles, ...model.allStyles.dimensionsStyles }
+ : { ...model.allStyles.fullStyle, overflow: 'auto' };
+
+ return (
+
+ {(value, onChange) => {
+ const customEvent = calculatedModel.eventHandlers;
+ const onChangeInternal = (...args: any[]): void => {
+ customEvent.onChange(args[0], args[1]);
+ if (typeof onChange === 'function')
+ onChange(...args);
+ };
+
+ return (
+
+ );
+ }}
+
+ );
+ },
+ settingsFormMarkup: getSettings,
+ validateSettings: (model) => validateConfigurableComponentSettings(getSettings, model),
+ migrator: (m) => m
+ .add(0, (prev) => ({
+ ...prev,
+ dataSourceType: prev['dataSourceType'] ?? 'values',
+ useRawValues: prev['useRawValues'] ?? false,
+ }))
+ .add(1, (prev) => {
+ return {
+ ...prev,
+ referenceListId: getLegacyReferenceListIdentifier(prev.referenceListNamespace, prev.referenceListName),
+ };
+ })
+ .add(2, (prev) => migratePropertyName(migrateCustomFunctions(prev)))
+ .add(3, (prev) => migrateVisibility(prev))
+ .add(4, (prev) => migrateReadOnly(prev))
+ .add(5, (prev, context) => ({
+ ...prev,
+ valueFormat: prev.valueFormat ??
+ context.isNew
+ ? 'simple'
+ : prev['useRawValue'] === true
+ ? 'simple'
+ : 'listItem',
+ editMode: prev?.editMode ?? 'inherited',
+ }))
+ .add(6, (prev) => ({ ...migrateFormApi.eventsAndProperties(prev) }))
+ .add(7, (prev) => {
+ const styles: IInputStyles = {
+ size: prev.size,
+ stylingBox: prev.stylingBox,
+ style: prev.style,
+ };
+
+ return { ...prev, desktop: { ...prev.desktop, ...styles }, tablet: { ...prev.tablet, ...styles }, mobile: { ...prev.mobile, ...styles } };
+ })
+ .add(8, (prev) => {
+ const styles: IInputStyles = {
+ size: prev.size,
+ width: prev.width,
+ height: prev.height,
+ hideBorder: prev.hideBorder,
+ borderSize: prev.borderSize,
+ borderRadius: prev.borderRadius,
+ borderColor: prev.borderColor,
+ fontSize: prev.fontSize,
+ fontColor: prev.fontColor,
+ backgroundColor: prev.backgroundColor,
+ stylingBox: prev.stylingBox,
+ };
+ return { ...prev, desktop: { ...prev.desktop, ...styles }, tablet: { ...prev.tablet, ...styles }, mobile: { ...prev.mobile, ...styles } };
+ })
+ .add(9, (prev) => ({ ...migratePrevStyles(prev, defaultStyles()) }))
+ .add(10, (prev) => {
+ const initTagStyle = migrateStyles({}, defaultTagStyles());
+
+ return {
+ ...prev,
+ tag: { ...initTagStyle },
+ showItemName: prev.showItemName ?? true,
+ showIcon: prev.showIcon ?? true,
+ solidColor: prev.solidColor ?? true,
+ displayStyle: prev.displayStyle ?? 'text',
+ desktop: { ...prev.desktop, tag: { ...initTagStyle } },
+ tablet: { ...prev.tablet, tag: { ...initTagStyle } },
+ mobile: { ...prev.mobile, tag: { ...initTagStyle } },
+ };
+ })
+ .add(11, (prev) => {
+ const result = { ...prev };
+ delete result['referenceListNamespace'];
+ delete result['referenceListName'];
+ const { referenceListId } = result;
+ const knownPrefixes = ["Shesha.Framework", "Shesha.Core", "Shesha.Scheduler"];
+ if (referenceListId && referenceListId.name && !referenceListId.module && knownPrefixes.some((p) => referenceListId.name.startsWith(p)))
+ result.referenceListId = { module: "Shesha", name: referenceListId.name };
+ return result;
+ }),
+ linkToModelMetadata: (model, metadata): IDropdownComponentProps => {
+ const isSingleRefList = metadata.dataType === DataTypes.referenceListItem;
+ const isMultipleRefList = metadata.dataType === DataTypes.array && metadata.dataFormat === ArrayFormats.multivalueReferenceList;
+
+ return {
+ ...model,
+ dataSourceType: isSingleRefList || isMultipleRefList ? 'referenceList' : 'values',
+ referenceListId: {
+ module: metadata.referenceListModule,
+ name: metadata.referenceListName,
+ },
+ mode: isMultipleRefList ? 'multiple' : 'single',
+ valueFormat: 'simple',
+ };
+ },
+};
+
+export default DropdownComponent;
diff --git a/shesha-reactjs/src/designer-components/editableTagGroup/index.tsx b/shesha-reactjs/src/designer-components/editableTagGroup/index.tsx
index 9e82d301d5..d2378e1b90 100644
--- a/shesha-reactjs/src/designer-components/editableTagGroup/index.tsx
+++ b/shesha-reactjs/src/designer-components/editableTagGroup/index.tsx
@@ -19,6 +19,7 @@ const EditableTagGroupComponent: EditableTagGroupComponentDefinition = {
icon: ,
isInput: true,
isOutput: true,
+ preserveDimensionsInDesigner: true,
dataTypeSupported: ({ dataType, dataFormat }) => dataType === DataTypes.array && dataFormat === ArrayFormats.simple,
Factory: ({ model }) => {
return (
diff --git a/shesha-reactjs/src/designer-components/entityPicker/index.tsx b/shesha-reactjs/src/designer-components/entityPicker/index.tsx
index ce77e1bc5d..8ab4585b02 100644
--- a/shesha-reactjs/src/designer-components/entityPicker/index.tsx
+++ b/shesha-reactjs/src/designer-components/entityPicker/index.tsx
@@ -58,6 +58,7 @@ const EntityPickerComponent: IToolboxComponent = {
isOutput: true,
name: 'Entity Picker',
icon: ,
+ preserveDimensionsInDesigner: true,
dataTypeSupported: ({ dataType, dataFormat }) =>
dataType === DataTypes.entityReference ||
(dataType === DataTypes.array && [ArrayFormats.entityReference, ArrayFormats.manyToManyEntities].includes(dataFormat)),
diff --git a/shesha-reactjs/src/designer-components/entityReference/entityReference.tsx b/shesha-reactjs/src/designer-components/entityReference/entityReference.tsx
index e4c2a349a0..fbb53c44fe 100644
--- a/shesha-reactjs/src/designer-components/entityReference/entityReference.tsx
+++ b/shesha-reactjs/src/designer-components/entityReference/entityReference.tsx
@@ -89,6 +89,7 @@ const EntityReferenceComponent: IToolboxComponent
isInput: true,
isOutput: true,
icon: ,
+ preserveDimensionsInDesigner: true,
Factory: ({ model: passedModel }) => {
const { allStyles, hidden, readOnly, ...model } = passedModel;
diff --git a/shesha-reactjs/src/designer-components/fileUpload/index.tsx b/shesha-reactjs/src/designer-components/fileUpload/index.tsx
index 0fde461ccc..24f8b14bb9 100644
--- a/shesha-reactjs/src/designer-components/fileUpload/index.tsx
+++ b/shesha-reactjs/src/designer-components/fileUpload/index.tsx
@@ -28,6 +28,8 @@ const FileUploadComponent: FileUploadComponentDefinition = {
icon: ,
isInput: true,
isOutput: true,
+ // FileUpload has its own intrinsic size and should not be forced to fill wrapper
+ preserveDimensionsInDesigner: true,
dataTypeSupported: ({ dataType }) => dataType === DataTypes.file,
Factory: ({ model }) => {
const { backendUrl } = useSheshaApplication();
diff --git a/shesha-reactjs/src/designer-components/fileUpload/settings.ts b/shesha-reactjs/src/designer-components/fileUpload/settings.ts
index 847678bb90..e1e0d23eda 100644
--- a/shesha-reactjs/src/designer-components/fileUpload/settings.ts
+++ b/shesha-reactjs/src/designer-components/fileUpload/settings.ts
@@ -18,11 +18,15 @@ export const getSettings: SettingsFormMarkupFactory = ({ fbf }) => {
.addContextPropertyAutocomplete({
id: '5c813b1a-04c5-4658-ac0f-cbcbae6b3bd4',
propertyName: 'propertyName',
+ label: 'Property Name',
parentId: 'abc54bf6-f76d-4139-a850-c99bf06c8b69',
- label: 'Property name',
+ description: "If left empty, the field will not be included in the submitted payload",
+ size: 'small',
+ styledLabel: true,
validate: {
required: true,
},
+ jsSetting: true,
})
.addTextField({
id: '46d07439-4c18-468c-89e1-60c002ce96c5',
diff --git a/shesha-reactjs/src/designer-components/fileUpload/utils.ts b/shesha-reactjs/src/designer-components/fileUpload/utils.ts
index ea93909fa3..4bc1b713c3 100644
--- a/shesha-reactjs/src/designer-components/fileUpload/utils.ts
+++ b/shesha-reactjs/src/designer-components/fileUpload/utils.ts
@@ -2,6 +2,12 @@ import { IStyleType } from '@/index';
export const defaultStyles = (): IStyleType => {
return {
+ font: {
+ type: 'Segoe UI',
+ align: 'left',
+ size: 14,
+ weight: '400',
+ },
border: {
hideBorder: false,
radiusType: 'all',
@@ -28,13 +34,6 @@ export const defaultStyles = (): IStyleType => {
storedFile: { id: null },
uploadFile: null,
},
- font: {
- color: '',
- type: 'Segoe UI',
- align: 'left',
- size: 14,
- weight: '400',
- },
shadow: {
offsetX: 0,
offsetY: 0,
@@ -44,3 +43,22 @@ export const defaultStyles = (): IStyleType => {
},
};
};
+
+export const containerDefaultStyles = (): IStyleType => {
+ return {
+ font: {
+ color: '',
+ type: 'Segoe UI',
+ align: 'left',
+ size: 14,
+ weight: '400',
+ },
+ dimensions: {
+ width: 'auto',
+ height: 'auto',
+ minHeight: '0px',
+ maxHeight: 'auto',
+ minWidth: '0px',
+ maxWidth: 'auto',
+ } };
+};
diff --git a/shesha-reactjs/src/designer-components/horizontalMenu/index.tsx b/shesha-reactjs/src/designer-components/horizontalMenu/index.tsx
index 9b9ae2392d..c6dc9fdea7 100644
--- a/shesha-reactjs/src/designer-components/horizontalMenu/index.tsx
+++ b/shesha-reactjs/src/designer-components/horizontalMenu/index.tsx
@@ -24,7 +24,7 @@ import { defaultStyles } from "./utils";
interface IMenuListProps extends IConfigurableFormComponent, ILayoutColor {
items?: ItemType[];
- overflow?: "dropdown" | "menu" | "scroll";
+ menuOverflow?: "dropdown" | "menu" | "scroll";
fontSize?: string;
gap?: string;
height?: string;
@@ -33,25 +33,6 @@ interface IMenuListProps extends IConfigurableFormComponent, ILayoutColor {
styleOnSelected?: string;
styleOnSubMenu?: string;
width?: string;
- dimensions?: {
- width?: string;
- height?: string;
- minWidth?: string;
- maxWidth?: string;
- minHeight?: string;
- maxHeight?: string;
- };
- font?: {
- type?: string;
- size?: number;
- weight?: string;
- color?: string;
- align?: string;
- };
- background?: {
- type?: string;
- color?: string;
- };
menuItemShadow?: {
color: string;
offsetX?: number;
@@ -61,6 +42,18 @@ interface IMenuListProps extends IConfigurableFormComponent, ILayoutColor {
};
}
+type MenuOverflowValue = "dropdown" | "menu" | "scroll";
+
+const resolveMenuOverflow = (
+ value: MenuOverflowValue | string | undefined,
+): "dropdown" | "menu" | "scroll" => {
+ if (value === "dropdown" || value === "menu" || value === "scroll") {
+ return value;
+ }
+
+ return "dropdown";
+};
+
interface ISideBarMenuProps {
items: ISidebarMenuItem[];
}
@@ -123,7 +116,6 @@ export const MenuListComponent: IToolboxComponent = {
...model.allStyles?.borderStyles,
...model.allStyles?.backgroundStyles,
...model.allStyles?.shadowStyles,
- ...model.allStyles?.overflowStyles,
...(model.containerStyle ? getStyle(model.containerStyle, data) : {}),
};
@@ -183,7 +175,7 @@ export const MenuListComponent: IToolboxComponent = {
styleOnSelected={getStyle(model?.styleOnSelected, data)}
styleOnSubMenu={getStyle(model?.styleOnSubMenu, data)}
menuItemStyle={menuItemShadowStyle}
- overflow={model.overflow || 'dropdown'}
+ overflow={resolveMenuOverflow(model.menuOverflow)}
width={width}
fontStyles={finalFontStyles as React.CSSProperties}
menuId={model.id}
@@ -202,7 +194,7 @@ export const MenuListComponent: IToolboxComponent = {
}))
.add(1, (prev) => ({
...prev,
- overflow: prev.overflow ?? 'dropdown',
+ menuOverflow: prev.menuOverflow ?? resolveMenuOverflow(prev.overflow as MenuOverflowValue | string | undefined),
})),
};
diff --git a/shesha-reactjs/src/designer-components/horizontalMenu/settings.ts b/shesha-reactjs/src/designer-components/horizontalMenu/settings.ts
index 8def614229..abce77e250 100644
--- a/shesha-reactjs/src/designer-components/horizontalMenu/settings.ts
+++ b/shesha-reactjs/src/designer-components/horizontalMenu/settings.ts
@@ -117,7 +117,7 @@ export const getSettings: SettingsFormMarkupFactory = ({ fbf }) => {
{
type: 'dropdown',
id: nanoid(),
- propertyName: 'overflow',
+ propertyName: 'menuOverflow',
label: 'Overflow',
size: 'small',
jsSetting: true,
diff --git a/shesha-reactjs/src/designer-components/iconPicker/iconPickerWrapper.tsx b/shesha-reactjs/src/designer-components/iconPicker/iconPickerWrapper.tsx
index 04cc18c907..656e01fef9 100644
--- a/shesha-reactjs/src/designer-components/iconPicker/iconPickerWrapper.tsx
+++ b/shesha-reactjs/src/designer-components/iconPicker/iconPickerWrapper.tsx
@@ -70,27 +70,18 @@ export const IconPickerWrapper: FC = (props) => {
return (
);
diff --git a/shesha-reactjs/src/designer-components/iconPicker/index.tsx b/shesha-reactjs/src/designer-components/iconPicker/index.tsx
index ec64858a40..057c08e9a2 100644
--- a/shesha-reactjs/src/designer-components/iconPicker/index.tsx
+++ b/shesha-reactjs/src/designer-components/iconPicker/index.tsx
@@ -19,6 +19,7 @@ const IconPickerComponent: IconPickerComponentDefinition = {
isInput: true,
isOutput: true,
canBeJsSetting: true,
+ preserveDimensionsInDesigner: true,
Factory: ({ model }) => {
const allData = useAvailableConstantsData();
diff --git a/shesha-reactjs/src/designer-components/image/image.tsx b/shesha-reactjs/src/designer-components/image/image.tsx
index a63ff0bf8c..48a300f742 100644
--- a/shesha-reactjs/src/designer-components/image/image.tsx
+++ b/shesha-reactjs/src/designer-components/image/image.tsx
@@ -3,6 +3,7 @@ import { App, Button, Image, Tooltip, Upload, UploadProps } from 'antd';
import { toBase64, useSheshaApplication, useStoredFile } from '@/index';
import { isFileTypeAllowed } from '@/utils/fileValidation';
import { DeleteOutlined, UploadOutlined } from '@ant-design/icons';
+import { useStyles } from './styles';
export type ImageSourceType = 'url' | 'storedFile' | 'base64';
@@ -22,6 +23,8 @@ export const ImageField: FC = (props) => {
const readOnly = props?.readOnly || props.imageSource === 'url';
+ const { styles: classes } = useStyles();
+
const { uploadFile, deleteFile, fileInfo } = useStoredFile(false) ?? {};
const { backendUrl, httpHeaders } = useSheshaApplication();
const { message } = App.useApp();
@@ -96,7 +99,7 @@ export const ImageField: FC = (props) => {
};
return (
-
+
{content && (
= {
icon: ,
isInput: true,
isOutput: true,
+ preserveDimensionsInDesigner: true,
calculateModel: (model, allData) => ({
ownerId: evaluateValueAsString(model.ownerId, allData),
dataId: (allData.data as { Id: string })?.Id, // TODO: review and remove
@@ -146,4 +147,3 @@ const ImageComponent: IToolboxComponent = {
export default ImageComponent;
-
diff --git a/shesha-reactjs/src/designer-components/image/settingsForm.ts b/shesha-reactjs/src/designer-components/image/settingsForm.ts
index ae3e1f17fd..e54c241784 100644
--- a/shesha-reactjs/src/designer-components/image/settingsForm.ts
+++ b/shesha-reactjs/src/designer-components/image/settingsForm.ts
@@ -352,10 +352,6 @@ export const getSettings: SettingsFormMarkupFactory = ({ fbf }) => {
value: "fill",
label: "Fill",
},
- {
- value: "auto",
- label: "Auto",
- },
],
},
{
diff --git a/shesha-reactjs/src/designer-components/image/styles.ts b/shesha-reactjs/src/designer-components/image/styles.ts
new file mode 100644
index 0000000000..336d824c66
--- /dev/null
+++ b/shesha-reactjs/src/designer-components/image/styles.ts
@@ -0,0 +1,13 @@
+import { createStyles } from '@/styles';
+
+export const useStyles = createStyles(({ css, cx }) => {
+ const imageWrapper = cx("sha-image-wrapper", css`
+ position: relative;
+ float: left;
+ width: 100%;
+ `);
+
+ return {
+ imageWrapper,
+ };
+});
diff --git a/shesha-reactjs/src/designer-components/image/utils.ts b/shesha-reactjs/src/designer-components/image/utils.ts
index 7d7adb415a..c71235368c 100644
--- a/shesha-reactjs/src/designer-components/image/utils.ts
+++ b/shesha-reactjs/src/designer-components/image/utils.ts
@@ -12,5 +12,6 @@ export const defaultStyles = (prev: IImageProps): IStyleType => {
},
radius: { all: borderRadius },
},
+ dimensions: { width: '100%', height: 'auto', minHeight: '0px', maxHeight: 'auto', minWidth: '0px', maxWidth: 'auto' },
};
};
diff --git a/shesha-reactjs/src/designer-components/imagePicker/index.tsx b/shesha-reactjs/src/designer-components/imagePicker/index.tsx
index 879121ff49..1bdb97a3a9 100644
--- a/shesha-reactjs/src/designer-components/imagePicker/index.tsx
+++ b/shesha-reactjs/src/designer-components/imagePicker/index.tsx
@@ -95,6 +95,7 @@ const ImagePickerComponent: IToolboxComponent = {
icon: ,
isInput: true,
isOutput: true,
+ preserveDimensionsInDesigner: true,
Factory: ({ model }) => {
return (
diff --git a/shesha-reactjs/src/designer-components/inputComponent/wrappers/textArea.tsx b/shesha-reactjs/src/designer-components/inputComponent/wrappers/textArea.tsx
index 1f12fe730e..6e0794daa3 100644
--- a/shesha-reactjs/src/designer-components/inputComponent/wrappers/textArea.tsx
+++ b/shesha-reactjs/src/designer-components/inputComponent/wrappers/textArea.tsx
@@ -12,7 +12,6 @@ export const TextAreaWrapper: FC = (props) => {
rows={2}
placeholder={placeholder}
size={size}
- style={{ top: '4px' }}
/>
);
};
diff --git a/shesha-reactjs/src/designer-components/link/index.tsx b/shesha-reactjs/src/designer-components/link/index.tsx
index 7b66b27412..03f0e85592 100644
--- a/shesha-reactjs/src/designer-components/link/index.tsx
+++ b/shesha-reactjs/src/designer-components/link/index.tsx
@@ -49,8 +49,8 @@ const LinkComponent: LinkComponentDefinition = {
{() => {
if (!hasChildren) {
return (
-
-
+
diff --git a/shesha-reactjs/src/designer-components/menuList/index.tsx b/shesha-reactjs/src/designer-components/menuList/index.tsx
index 58f0e9e3f0..9d2f37a68c 100644
--- a/shesha-reactjs/src/designer-components/menuList/index.tsx
+++ b/shesha-reactjs/src/designer-components/menuList/index.tsx
@@ -53,6 +53,7 @@ export const MenuListComponent: IToolboxComponent = {
name: "Menu List",
isInput: false,
isOutput: false,
+ preserveDimensionsInDesigner: true,
icon: ,
Factory: ({ model }) => {
const { data } = useFormData();
diff --git a/shesha-reactjs/src/designer-components/metadataEditor/index.tsx b/shesha-reactjs/src/designer-components/metadataEditor/index.tsx
index fb54fee365..52b0e5270f 100644
--- a/shesha-reactjs/src/designer-components/metadataEditor/index.tsx
+++ b/shesha-reactjs/src/designer-components/metadataEditor/index.tsx
@@ -1,54 +1,55 @@
-import ConfigurableFormItem from '@/components/formDesigner/components/formItem';
-import React, { useMemo } from 'react';
-import settingsFormJson from './settingsForm.json';
-import { ApartmentOutlined } from '@ant-design/icons';
-import { FormMarkup } from '@/providers/form/models';
-import { executeScriptSync, validateConfigurableComponentSettings } from '@/providers/form/utils';
-import { IToolboxComponent } from '@/interfaces';
-import { IMetadataEditorComponentProps } from './interfaces';
-import { MetadataEditor } from './metadataEditor';
-import { IPropertyMetadata } from '@/interfaces/metadata';
-import { useFormData } from '@/providers';
-import { useMetadataBuilderFactory } from '@/utils/metadata/hooks';
-
-const settingsForm = settingsFormJson as FormMarkup;
-
-export const MetadataEditorComponent: IToolboxComponent = {
- type: 'metadataEditor',
- isInput: true,
- isOutput: true,
- canBeJsSetting: false,
- name: 'Metadata editor',
- icon: ,
- Factory: ({ model }) => {
- const { data: formData } = useFormData();
- const metadataBuilderFactory = useMetadataBuilderFactory();
-
- const baseProperties = useMemo(() => {
- if (!model.baseProperties)
- return [];
-
- const metadataBuilder = metadataBuilderFactory();
- const result = executeScriptSync(model.baseProperties, { data: formData, metadataBuilder });
- return result;
- }, [model.baseProperties, formData]);
-
- return (
-
- {(value, onChange) => {
- return (
-
- );
- }}
-
- );
- },
- settingsFormMarkup: settingsForm,
- validateSettings: (model) => validateConfigurableComponentSettings(settingsForm, model),
-};
+import ConfigurableFormItem from '@/components/formDesigner/components/formItem';
+import React, { useMemo } from 'react';
+import settingsFormJson from './settingsForm.json';
+import { ApartmentOutlined } from '@ant-design/icons';
+import { FormMarkup } from '@/providers/form/models';
+import { executeScriptSync, validateConfigurableComponentSettings } from '@/providers/form/utils';
+import { IToolboxComponent } from '@/interfaces';
+import { IMetadataEditorComponentProps } from './interfaces';
+import { MetadataEditor } from './metadataEditor';
+import { IPropertyMetadata } from '@/interfaces/metadata';
+import { useFormData } from '@/providers';
+import { useMetadataBuilderFactory } from '@/utils/metadata/hooks';
+
+const settingsForm = settingsFormJson as FormMarkup;
+
+export const MetadataEditorComponent: IToolboxComponent = {
+ type: 'metadataEditor',
+ isInput: true,
+ isOutput: true,
+ canBeJsSetting: false,
+ name: 'Metadata editor',
+ icon: ,
+ preserveDimensionsInDesigner: true,
+ Factory: ({ model }) => {
+ const { data: formData } = useFormData();
+ const metadataBuilderFactory = useMetadataBuilderFactory();
+
+ const baseProperties = useMemo(() => {
+ if (!model.baseProperties)
+ return [];
+
+ const metadataBuilder = metadataBuilderFactory();
+ const result = executeScriptSync(model.baseProperties, { data: formData, metadataBuilder });
+ return result;
+ }, [model.baseProperties, formData]);
+
+ return (
+
+ {(value, onChange) => {
+ return (
+
+ );
+ }}
+
+ );
+ },
+ settingsFormMarkup: settingsForm,
+ validateSettings: (model) => validateConfigurableComponentSettings(settingsForm, model),
+};
diff --git a/shesha-reactjs/src/designer-components/multiColorInput/index.tsx b/shesha-reactjs/src/designer-components/multiColorInput/index.tsx
index 169dd59899..2207983178 100644
--- a/shesha-reactjs/src/designer-components/multiColorInput/index.tsx
+++ b/shesha-reactjs/src/designer-components/multiColorInput/index.tsx
@@ -66,7 +66,7 @@ export const MultiColorInput = ({ value = {}, onChange, readOnly, propertyName }
}}
disabled={readOnly}
icon={}
- style={{ marginTop: '5px' }}
+ style={{ margin: '5px 0px' }}
>
diff --git a/shesha-reactjs/src/designer-components/numberField/numberField.tsx b/shesha-reactjs/src/designer-components/numberField/numberField.tsx
index 72178335bc..e3813ab10c 100644
--- a/shesha-reactjs/src/designer-components/numberField/numberField.tsx
+++ b/shesha-reactjs/src/designer-components/numberField/numberField.tsx
@@ -29,6 +29,7 @@ const NumberFieldComponent: NumberFieldComponentDefinition = {
canBeJsSetting: true,
name: 'Number field',
icon: ,
+ preserveDimensionsInDesigner: true,
dataTypeSupported: ({ dataType }) => dataType === DataTypes.number,
calculateModel: (model, allData) => {
return {
diff --git a/shesha-reactjs/src/designer-components/numberField/styles.ts b/shesha-reactjs/src/designer-components/numberField/styles.ts
index e8a13ab74e..bd4e764d29 100644
--- a/shesha-reactjs/src/designer-components/numberField/styles.ts
+++ b/shesha-reactjs/src/designer-components/numberField/styles.ts
@@ -16,7 +16,7 @@ export const useStyles = createStyles(({ css, cx }, { fontWeight, fontFamily, te
.ant-input-number-input {
--ant-color-text: ${color} !important;
--ant-font-size: ${fontSize} !important;
- font-size: ${addPx(fontSize)} !important;
+ font-size: ${addPx(fontSize) ?? 'inherit'} !important;
font-weight: ${fontWeight} !important;
font-family: ${fontFamily};
text-align: ${textAlign};
diff --git a/shesha-reactjs/src/designer-components/passwordCombo/index.tsx b/shesha-reactjs/src/designer-components/passwordCombo/index.tsx
index f98f37d6b8..510a0c1c56 100644
--- a/shesha-reactjs/src/designer-components/passwordCombo/index.tsx
+++ b/shesha-reactjs/src/designer-components/passwordCombo/index.tsx
@@ -25,6 +25,7 @@ const PasswordComboComponent: IToolboxComponent = {
type: 'passwordCombo',
isInput: true,
name: 'Password combo',
+ preserveDimensionsInDesigner: true,
icon: ,
dataTypeSupported: ({ dataType, dataFormat }) =>
dataType === DataTypes.string && dataFormat === StringFormats.password,
diff --git a/shesha-reactjs/src/designer-components/progress/progressWrapper.tsx b/shesha-reactjs/src/designer-components/progress/progressWrapper.tsx
index b472e609b5..91c46f9b9b 100644
--- a/shesha-reactjs/src/designer-components/progress/progressWrapper.tsx
+++ b/shesha-reactjs/src/designer-components/progress/progressWrapper.tsx
@@ -3,5 +3,5 @@ import { IValuable } from '@/interfaces';
import { Progress, ProgressProps } from 'antd';
export const ProgressWrapper: FC = ({ percent, value, ...props }) => {
- return ;
-};
+ return ;
+};
diff --git a/shesha-reactjs/src/designer-components/propertyAutocomplete/index.tsx b/shesha-reactjs/src/designer-components/propertyAutocomplete/index.tsx
index 9af175968d..d7b1021cf9 100644
--- a/shesha-reactjs/src/designer-components/propertyAutocomplete/index.tsx
+++ b/shesha-reactjs/src/designer-components/propertyAutocomplete/index.tsx
@@ -19,6 +19,7 @@ export const PropertyAutocompleteComponent: PropertyAutocompleteComponentDefinit
icon: ,
isInput: true,
isOutput: true,
+ preserveDimensionsInDesigner: true,
calculateModel: (model, allData) => ({
modelType: typeof model.modelType === 'string' ? evaluateString(model.modelType, { data: allData.data }) : model.modelType,
dropdownStyle: getStyle(model.dropdownStyle, allData.data),
diff --git a/shesha-reactjs/src/designer-components/radio/radio.tsx b/shesha-reactjs/src/designer-components/radio/radio.tsx
index 66dbea265f..bac3641830 100644
--- a/shesha-reactjs/src/designer-components/radio/radio.tsx
+++ b/shesha-reactjs/src/designer-components/radio/radio.tsx
@@ -24,17 +24,17 @@ const RadioComponent: RadioComponentDefinition = {
isInput: true,
isOutput: true,
canBeJsSetting: true,
+ // Radio has its own intrinsic size and should not be forced to fill wrapper
+ preserveDimensionsInDesigner: true,
dataTypeSupported: ({ dataType, dataFormat }) => dataType === DataTypes.referenceListItem || (dataType === DataTypes.array && dataFormat === ArrayFormats.simple),
calculateModel: (model, allData) => ({
eventHandlers: getAllEventHandlers(model, allData),
dataSourceUrl: model.dataSourceUrl ? executeScriptSync(model.dataSourceUrl, allData) : model.dataSourceUrl,
defaultValue: evaluateValue(model.defaultValue, allData.data),
}),
- Factory: ({ model, calculatedModel }) => {
- const { style, ...restProps } = model;
-
+ Factory: ({ model, calculatedModel }) => {
return (
-
+
{(value, onChange) => {
const customEvents = calculatedModel.eventHandlers;
const onChangeInternal = (e: any): void => {
@@ -44,7 +44,7 @@ const RadioComponent: RadioComponentDefinition = {
return (
= {
icon: ,
isInput: true,
isOutput: true,
+ preserveDimensionsInDesigner: true,
calculateModel: (model, allData) => ({
eventHandlers: getAllEventHandlers(model, allData),
}),
@@ -61,7 +62,7 @@ const RateComponent: IToolboxComponent = {
count={localCount ?? 5}
tooltips={tooltips}
className={classNames(className, 'sha-rate')}
- style={model.allStyles.fullStyle}
+ style={{ ...model.allStyles?.fullStyle, display: 'flex', alignItems: 'center' }}
{...customEvent}
value={value}
onChange={onChangeInternal}
diff --git a/shesha-reactjs/src/designer-components/refListStatus/index.tsx b/shesha-reactjs/src/designer-components/refListStatus/index.tsx
index 3229f9e073..d2c8691bbd 100644
--- a/shesha-reactjs/src/designer-components/refListStatus/index.tsx
+++ b/shesha-reactjs/src/designer-components/refListStatus/index.tsx
@@ -17,8 +17,10 @@ import { defaultStyles } from './utils';
const RefListStatusComponent: IToolboxComponent = {
type: 'refListStatus',
- isInput: false,
+ isInput: true,
isOutput: true,
+ // Component manages its own dimensions in designer mode
+ preserveDimensionsInDesigner: true,
name: 'Reference list status',
icon: ,
Factory: ({ model }) => {
@@ -49,6 +51,7 @@ const RefListStatusComponent: IToolboxComponent = {
showReflistName={showReflistName}
solidBackground={solidBackground}
style={model.allStyles?.fullStyle ?? {}}
+ isDesigner={formMode === 'designer'}
/>
);
}}
diff --git a/shesha-reactjs/src/designer-components/richTextEditor/index.tsx b/shesha-reactjs/src/designer-components/richTextEditor/index.tsx
index 8b0a64f74a..2595b04789 100644
--- a/shesha-reactjs/src/designer-components/richTextEditor/index.tsx
+++ b/shesha-reactjs/src/designer-components/richTextEditor/index.tsx
@@ -1,120 +1,131 @@
-import React from 'react';
-import RichTextEditor from '@/components/richTextEditor';
-import settingsFormJson from './settingsForm.json';
-import { ConfigurableFormItem } from '@/components';
-import { EditOutlined } from '@ant-design/icons';
-import { FormMarkup } from '@/providers/form/models';
-import { getStyle } from '@/providers/form/utils';
-import { IJoditEditorProps } from '@/components/richTextEditor/joditEditor';
-import { IRichTextEditorProps } from './interfaces';
-import { IToolboxComponent } from '@/interfaces/formDesigner';
-import {
- migrateCustomFunctions,
- migratePropertyName,
- migrateReadOnly,
-} from '@/designer-components/_common-migrations/migrateSettings';
-import { useDeepCompareMemoKeepReference } from '@/hooks';
-import { useForm, useFormData } from '@/providers';
-import { validateConfigurableComponentSettings } from '@/formDesignerUtils';
-import { migrateFormApi } from '../_common-migrations/migrateFormApi1';
-import { getSettings } from './formSettings';
-import { migratePrevStyles } from '../_common-migrations/migrateStyles';
-import { defaultStyles } from './utils';
-
-const settingsForm = settingsFormJson as FormMarkup;
-
-type PartialRichTextEditorConfig = Partial;
-
-const RichTextEditorComponent: IToolboxComponent = {
- type: 'richTextEditor',
- name: 'Rich Text Editor',
- icon: ,
- isInput: true,
- isOutput: true,
- Factory: ({ model }) => {
- const { data: formData } = useFormData();
- const { allStyles } = model;
- const { width, height, minWidth, minHeight, maxWidth, maxHeight } = allStyles?.dimensionsStyles;
-
- const { formMode } = useForm();
-
-
- const config = useDeepCompareMemoKeepReference(() => {
- const typedConfig: PartialRichTextEditorConfig = {
- toolbar: model?.toolbar,
- preset: model?.preset,
- textIcons: model?.textIcons,
- toolbarButtonSize: model?.toolbarButtonSize,
- theme: typeof model?.theme === 'string' ? model?.theme : 'default',
- iframe: model?.iframe,
- direction: model?.direction,
- disablePlugins: [...(model?.disablePlugins || []), 'spellcheck'].join(','),
- ...(!model.autoHeight && { height, minHeight, maxHeight }),
- ...(!model.autoWidth && { width, minWidth, maxWidth }),
- placeholder: model?.placeholder ?? '',
- readonly: model?.readOnly,
- style: getStyle(model?.style, formData),
- defaultActionOnPaste: 'insert_as_html',
- enter: model?.enter || 'br',
- editHTMLDocumentMode: false,
- enterBlock: 'div',
- colorPickerDefaultTab: 'color',
- allowResizeX: model?.allowResizeX && !model?.autoWidth,
- allowResizeY: model?.allowResizeY && !model?.autoHeight,
- askBeforePasteHTML: model?.askBeforePasteHTML,
- askBeforePasteFromWord: model?.askBeforePasteFromWord,
- autofocus: formMode === 'designer' ? false : model?.autofocus,
- showCharsCounter: model?.showCharsCounter,
- showWordsCounter: model?.showWordsCounter,
- };
- return typedConfig;
- }, [model, model.readOnly]);
-
- const rerenderKey = `${model?.placeholder || ''}-${model?.placeholder || false}`;
-
- return (
-
- {(value, onChange) => }
-
- );
- },
- settingsFormMarkup: getSettings,
- validateSettings: (model) => validateConfigurableComponentSettings(settingsForm, model),
- initModel: (model) => ({
- ...model,
- showCharsCounter: true,
- showWordsCounter: true,
- showXPathInStatusbar: true,
- minHeight: 200,
- minWidth: 200,
- toolbar: true,
- useSearch: true,
- autoHeight: true,
- autoWidth: true,
- askBeforePasteHTML: true,
- askBeforePasteFromWord: true,
- disablePlugins: null,
- }),
- migrator: (m) =>
- m
- .add(0, (prev) => migratePropertyName(migrateCustomFunctions(prev)))
- .add(1, (prev) => migrateReadOnly(prev))
- .add(2, (prev) => ({ ...migrateFormApi.eventsAndProperties(prev) }))
- .add(3, (prev) => {
- const styles = {
- style: prev.style,
- theme: prev.theme,
- autoHeight: prev.autoHeight ?? true,
- autoWidth: prev.autoWidth ?? true,
- };
- const resize = {
- allowResizeX: true,
- allowResizeY: true,
- };
- return { ...prev, desktop: { ...styles }, tablet: { ...styles }, mobile: { ...styles }, ...resize };
- })
- .add(6, (prev) => ({ ...migratePrevStyles(prev, defaultStyles()) })),
-
-};
-
-export default RichTextEditorComponent;
+import React from 'react';
+import RichTextEditor from '@/components/richTextEditor';
+import settingsFormJson from './settingsForm.json';
+import { ConfigurableFormItem } from '@/components';
+import { EditOutlined } from '@ant-design/icons';
+import { FormMarkup } from '@/providers/form/models';
+import { getStyle } from '@/providers/form/utils';
+import { IJoditEditorProps } from '@/components/richTextEditor/joditEditor';
+import { IRichTextEditorProps } from './interfaces';
+import { IToolboxComponent } from '@/interfaces/formDesigner';
+import {
+ migrateCustomFunctions,
+ migratePropertyName,
+ migrateReadOnly,
+} from '@/designer-components/_common-migrations/migrateSettings';
+import { useDeepCompareMemoKeepReference } from '@/hooks';
+import { useForm, useFormData } from '@/providers';
+import { validateConfigurableComponentSettings } from '@/formDesignerUtils';
+import { migrateFormApi } from '../_common-migrations/migrateFormApi1';
+import { getSettings } from './formSettings';
+import { migratePrevStyles } from '../_common-migrations/migrateStyles';
+import { defaultStyles } from './utils';
+
+const settingsForm = settingsFormJson as FormMarkup;
+
+type PartialRichTextEditorConfig = Partial;
+
+const RichTextEditorComponent: IToolboxComponent = {
+ type: 'richTextEditor',
+ name: 'Rich Text Editor',
+ icon: ,
+ isInput: true,
+ isOutput: true,
+ preserveDimensionsInDesigner: true,
+ Factory: ({ model }) => {
+ const { data: formData } = useFormData();
+ const { allStyles } = model;
+ const { width, height, minWidth, minHeight, maxWidth, maxHeight } = allStyles?.dimensionsStyles ?? {};
+
+ const { formMode } = useForm();
+
+
+ const config = useDeepCompareMemoKeepReference(() => {
+ const typedConfig: PartialRichTextEditorConfig = {
+ toolbar: model?.toolbar,
+ preset: model?.preset,
+ textIcons: model?.textIcons,
+ toolbarButtonSize: model?.toolbarButtonSize,
+ theme: typeof model?.theme === 'string' ? model?.theme : 'default',
+ iframe: model?.iframe,
+ direction: model?.direction,
+ disablePlugins: [...(model?.disablePlugins || []), 'spellcheck'].join(','),
+ placeholder: model?.placeholder ?? '',
+ readonly: model?.readOnly,
+ style: getStyle(model?.style, formData),
+ defaultActionOnPaste: 'insert_as_html',
+ enter: model?.enter || 'br',
+ editHTMLDocumentMode: false,
+ ...(!model.autoHeight && { height, minHeight, maxHeight }),
+ ...(!model.autoWidth && { width, minWidth, maxWidth }),
+ enterBlock: 'div',
+ colorPickerDefaultTab: 'color',
+ allowResizeX: model?.allowResizeX && !model?.autoWidth,
+ allowResizeY: model?.allowResizeY && !model?.autoHeight,
+ askBeforePasteHTML: model?.askBeforePasteHTML,
+ askBeforePasteFromWord: model?.askBeforePasteFromWord,
+ autofocus: formMode === 'designer' ? false : model?.autofocus,
+ showCharsCounter: model?.showCharsCounter,
+ showWordsCounter: model?.showWordsCounter,
+ };
+ return typedConfig;
+ }, [model, formData, formMode]);
+
+ const rerenderKey = `${model?.placeholder || ''}-${model?.placeholder || false}`;
+
+ return (
+
+ {(value, onChange) => (
+
+ )}
+
+ );
+ },
+ settingsFormMarkup: getSettings,
+ validateSettings: (model) => validateConfigurableComponentSettings(settingsForm, model),
+ initModel: (model) => ({
+ ...model,
+ showCharsCounter: true,
+ showWordsCounter: true,
+ showXPathInStatusbar: true,
+ minHeight: 200,
+ minWidth: 200,
+ toolbar: true,
+ useSearch: true,
+ autoHeight: true,
+ autoWidth: true,
+ askBeforePasteHTML: true,
+ askBeforePasteFromWord: true,
+ disablePlugins: null,
+ }),
+ migrator: (m) =>
+ m
+ .add(0, (prev) => migratePropertyName(migrateCustomFunctions(prev)))
+ .add(1, (prev) => migrateReadOnly(prev))
+ .add(2, (prev) => ({ ...migrateFormApi.eventsAndProperties(prev) }))
+ .add(3, (prev) => {
+ const styles = {
+ style: prev.style,
+ theme: prev.theme,
+ autoHeight: prev.autoHeight ?? true,
+ autoWidth: prev.autoWidth ?? true,
+ };
+ const resize = {
+ allowResizeX: true,
+ allowResizeY: true,
+ };
+ return { ...prev, desktop: { ...styles }, tablet: { ...styles }, mobile: { ...styles }, ...resize };
+ })
+ .add(6, (prev) => ({ ...migratePrevStyles(prev, defaultStyles()) })),
+
+};
+
+export default RichTextEditorComponent;
diff --git a/shesha-reactjs/src/designer-components/sectionSeprator/index.tsx b/shesha-reactjs/src/designer-components/sectionSeprator/index.tsx
index 7ddcc185a1..9f4f4be4e0 100644
--- a/shesha-reactjs/src/designer-components/sectionSeprator/index.tsx
+++ b/shesha-reactjs/src/designer-components/sectionSeprator/index.tsx
@@ -42,7 +42,7 @@ const SectionSeparatorComponent: SectionSeparatorComponentDefinition = {
...getStyle(model?.containerStyle, formData),
};
- const dimensions = getDimensionsStyle(extractedDimensions, containerAdditionalStyles);
+ const dimensions = getDimensionsStyle(extractedDimensions);
const inputProps = {
...model,
diff --git a/shesha-reactjs/src/designer-components/settingsInput/index.tsx b/shesha-reactjs/src/designer-components/settingsInput/index.tsx
index e5382a1816..6761bb9144 100644
--- a/shesha-reactjs/src/designer-components/settingsInput/index.tsx
+++ b/shesha-reactjs/src/designer-components/settingsInput/index.tsx
@@ -13,7 +13,6 @@ const SettingsInput: SettingsInputDefinition = {
Factory: ({ model }) => {
const { label, propertyName: property, tooltip: description, readOnly } = model;
-
return (
model.hidden ? null
: (
diff --git a/shesha-reactjs/src/designer-components/settingsInput/settingsInput.tsx b/shesha-reactjs/src/designer-components/settingsInput/settingsInput.tsx
index 11928db293..4a01d1846a 100644
--- a/shesha-reactjs/src/designer-components/settingsInput/settingsInput.tsx
+++ b/shesha-reactjs/src/designer-components/settingsInput/settingsInput.tsx
@@ -46,7 +46,7 @@ export const SettingInput: React.FC = (props) => {
return isHidden ? null
: (
-
+
{content}}
diff --git a/shesha-reactjs/src/designer-components/slider/index.tsx b/shesha-reactjs/src/designer-components/slider/index.tsx
index ba7b42bef1..7923f33b0f 100644
--- a/shesha-reactjs/src/designer-components/slider/index.tsx
+++ b/shesha-reactjs/src/designer-components/slider/index.tsx
@@ -17,6 +17,7 @@ const SliderComponent: SliderComponentDefinition = {
isInput: true,
isOutput: true,
canBeJsSetting: true,
+ preserveDimensionsInDesigner: true,
dataTypeSupported: ({ dataType, dataFormat }) => dataType === DataTypes.number && [NumberFormats.int64, NumberFormats.int32].includes(dataFormat),
Factory: ({ model }) => {
const { data: formData } = useFormData();
diff --git a/shesha-reactjs/src/designer-components/sortingEditor/index.tsx b/shesha-reactjs/src/designer-components/sortingEditor/index.tsx
index 3d98863980..8de68671f5 100644
--- a/shesha-reactjs/src/designer-components/sortingEditor/index.tsx
+++ b/shesha-reactjs/src/designer-components/sortingEditor/index.tsx
@@ -1,47 +1,48 @@
-import { FormMarkup, IConfigurableFormComponent } from '@/providers/form/models';
-import { IToolboxComponent } from '@/interfaces';
-import React from 'react';
-import { GroupOutlined } from '@ant-design/icons';
-import { ConfigurableFormItem } from '@/components/index';
-import settingsFormJson from './settingsForm.json';
-import { validateConfigurableComponentSettings, evaluateString } from '@/providers/form/utils';
-
-import { SortingEditor } from '@/components/dataTable/sortingConfigurator/index';
-import { MetadataProvider } from '@/providers/index';
-import { migrateReadOnly } from '../_common-migrations/migrateSettings';
-import ConditionalWrap from '@/components/conditionalWrapper';
-import { IEntityTypeIdentifier } from '@/providers/sheshaApplication/publicApi/entities/models';
-
-export interface ISortingEditorComponentProps extends IConfigurableFormComponent {
- modelType: string | IEntityTypeIdentifier;
- maxItemsCount?: number;
-}
-
-const settingsForm = settingsFormJson as FormMarkup;
-
-export const SortingEditorComponent: IToolboxComponent = {
- type: 'dataSortingEditor',
- name: 'Data Sorting Editor',
- isInput: true,
- isOutput: true,
- canBeJsSetting: true,
- icon: ,
- calculateModel: (model, allData) => ({ modelType: typeof model.modelType === 'string' ? evaluateString(model.modelType, { data: allData.data }) : model.modelType }),
- Factory: ({ model, calculatedModel }) => {
- return (
- {content}}
- >
-
- {(value, onChange) => }
-
-
- );
- },
- settingsFormMarkup: settingsForm,
- validateSettings: (model) => validateConfigurableComponentSettings(settingsForm, model),
- migrator: (m) => m
- .add(0, (prev) => ({ ...prev, modelType: '' }))
- .add(1, (prev) => migrateReadOnly(prev)),
-};
+import { FormMarkup, IConfigurableFormComponent } from '@/providers/form/models';
+import { IToolboxComponent } from '@/interfaces';
+import React from 'react';
+import { GroupOutlined } from '@ant-design/icons';
+import { ConfigurableFormItem } from '@/components/index';
+import settingsFormJson from './settingsForm.json';
+import { validateConfigurableComponentSettings, evaluateString } from '@/providers/form/utils';
+
+import { SortingEditor } from '@/components/dataTable/sortingConfigurator/index';
+import { MetadataProvider } from '@/providers/index';
+import { migrateReadOnly } from '../_common-migrations/migrateSettings';
+import ConditionalWrap from '@/components/conditionalWrapper';
+import { IEntityTypeIdentifier } from '@/providers/sheshaApplication/publicApi/entities/models';
+
+export interface ISortingEditorComponentProps extends IConfigurableFormComponent {
+ modelType: string | IEntityTypeIdentifier;
+ maxItemsCount?: number;
+}
+
+const settingsForm = settingsFormJson as FormMarkup;
+
+export const SortingEditorComponent: IToolboxComponent = {
+ type: 'dataSortingEditor',
+ name: 'Data Sorting Editor',
+ isInput: true,
+ isOutput: true,
+ canBeJsSetting: true,
+ icon: ,
+ preserveDimensionsInDesigner: true,
+ calculateModel: (model, allData) => ({ modelType: typeof model.modelType === 'string' ? evaluateString(model.modelType, { data: allData.data }) : model.modelType }),
+ Factory: ({ model, calculatedModel }) => {
+ return (
+ {content}}
+ >
+
+ {(value, onChange) => }
+
+
+ );
+ },
+ settingsFormMarkup: settingsForm,
+ validateSettings: (model) => validateConfigurableComponentSettings(settingsForm, model),
+ migrator: (m) => m
+ .add(0, (prev) => ({ ...prev, modelType: '' }))
+ .add(1, (prev) => migrateReadOnly(prev)),
+};
diff --git a/shesha-reactjs/src/designer-components/statistic/index.tsx b/shesha-reactjs/src/designer-components/statistic/index.tsx
index 363b80254e..6fd657714d 100644
--- a/shesha-reactjs/src/designer-components/statistic/index.tsx
+++ b/shesha-reactjs/src/designer-components/statistic/index.tsx
@@ -48,6 +48,7 @@ const StatisticComponent: IToolboxComponent = {
icon: ,
isInput: true,
isOutput: true,
+ preserveDimensionsInDesigner: ["height"],
Factory: ({ model: passedModel }) => {
const { style, valueStyle, titleStyle, prefix, suffix, prefixIcon, suffixIcon, ...model } = passedModel;
const allData = useAvailableConstantsData();
diff --git a/shesha-reactjs/src/designer-components/styleBox/components/utils.ts b/shesha-reactjs/src/designer-components/styleBox/components/utils.ts
index a908df1749..9da62002d5 100644
--- a/shesha-reactjs/src/designer-components/styleBox/components/utils.ts
+++ b/shesha-reactjs/src/designer-components/styleBox/components/utils.ts
@@ -16,7 +16,19 @@ export const getStyleChangeValue = (
});
};
-export const getStyleValue = (type: keyof IValue, direction: keyof IInputDirection, value: string): number => {
- const v = jsonSafeParse(value || '{}') as IValue;
- return (v || {})[`${type}${capitalizeFirstLetter(direction)}`] || 0;
+export const getStyleValue = (type: keyof IValue, direction: keyof IInputDirection, value: string): string => {
+ const parsed = jsonSafeParse(value || '{}');
+
+ // Compute the dynamic key (e.g., "paddingTop", "marginLeft")
+ const key = `${type}${capitalizeFirstLetter(direction)}`;
+
+ // Runtime checks: ensure parsed is an object, property exists, and value is a string
+ if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
+ const propValue = (parsed as Record)[key];
+ if (typeof propValue === 'string') {
+ return propValue;
+ }
+ }
+
+ return '0';
};
diff --git a/shesha-reactjs/src/designer-components/styleLabel/index.tsx b/shesha-reactjs/src/designer-components/styleLabel/index.tsx
index 5aae6e5eac..7aa8735612 100644
--- a/shesha-reactjs/src/designer-components/styleLabel/index.tsx
+++ b/shesha-reactjs/src/designer-components/styleLabel/index.tsx
@@ -4,6 +4,7 @@ import ConfigurableFormItem from '@/components/formDesigner/components/formItem'
import LabelConfiguratorComponent from './labelConfigurator';
import { LabelConfiguratorDefinition } from './interfaces';
import { getSettings } from './settings';
+import { useStyles } from './styles';
const LabelConfigurator: LabelConfiguratorDefinition = {
type: 'labelConfigurator',
@@ -13,10 +14,15 @@ const LabelConfigurator: LabelConfiguratorDefinition = {
canBeJsSetting: true,
icon: ,
Factory: ({ model }) => {
+ const { styles } = useStyles();
+
return (
-
-
-
+
+
+
+
+
+
);
},
settingsFormMarkup: getSettings,
diff --git a/shesha-reactjs/src/designer-components/styleLabel/labelConfigurator.tsx b/shesha-reactjs/src/designer-components/styleLabel/labelConfigurator.tsx
index 4e0d4bdb95..2b37849103 100644
--- a/shesha-reactjs/src/designer-components/styleLabel/labelConfigurator.tsx
+++ b/shesha-reactjs/src/designer-components/styleLabel/labelConfigurator.tsx
@@ -7,14 +7,14 @@ import { IRadioOption } from '../settingsInput/interfaces';
export interface ILabelProps {
readOnly?: boolean;
- onChange?: (value: any) => void;
- value?: boolean;
label: string | React.ReactNode;
+ value?: boolean;
labelAlignOptions?: IRadioOption[];
}
const LabelConfiguratorComponent: FC = ({ value, readOnly, label, labelAlignOptions: labelAlign }) => {
const { styles } = useStyles();
+
return (
<>
diff --git a/shesha-reactjs/src/designer-components/styleLabel/styles.ts b/shesha-reactjs/src/designer-components/styleLabel/styles.ts
index 7b8005bd82..616bc42f12 100644
--- a/shesha-reactjs/src/designer-components/styleLabel/styles.ts
+++ b/shesha-reactjs/src/designer-components/styleLabel/styles.ts
@@ -8,8 +8,9 @@ export const useStyles = createStyles(({ css, cx, token }) => {
gap: 8px;
position: absolute;
justify-content: flex-end;
- top: 0px;
right: 30px;
+ top: 4px;
+ z-index: 2;
`);
const hidelLabelIcon = cx("", css`
@@ -23,8 +24,22 @@ export const useStyles = createStyles(({ css, cx, token }) => {
border: 1px solid ${token.colorPrimary};
`);
+ const formItem = cx("", css`
+ .ant-form-item {
+ margin: 0px !important;
+ }
+ .sha-js-label {
+ margin: 0px !important;
+ }
+
+ .ant-form-item-control-input {
+ min-height: 0px !important;
+ }
+ `);
+
return {
flexWrapper,
hidelLabelIcon,
+ formItem,
};
});
diff --git a/shesha-reactjs/src/designer-components/switch/switch.tsx b/shesha-reactjs/src/designer-components/switch/switch.tsx
index 0b322a6439..28bcdc4487 100644
--- a/shesha-reactjs/src/designer-components/switch/switch.tsx
+++ b/shesha-reactjs/src/designer-components/switch/switch.tsx
@@ -7,7 +7,7 @@ import { validateConfigurableComponentSettings } from '@/providers/form/utils';
import { SwitcherOutlined } from '@ant-design/icons';
import { Switch } from 'antd';
import { SwitchChangeEventHandler, SwitchSize } from 'antd/lib/switch';
-import React, { useMemo } from 'react';
+import React from 'react';
import { migrateFormApi } from '../_common-migrations/migrateFormApi1';
import { ISwitchComponentProps, SwitchComponentDefinition } from './interfaces';
import { getSettings } from './settingsForm';
@@ -19,14 +19,9 @@ const SwitchComponent: SwitchComponentDefinition = {
isInput: true,
isOutput: true,
canBeJsSetting: true,
+ preserveDimensionsInDesigner: true,
calculateModel: (model, allData) => ({ eventHandlers: getAllEventHandlers(model, allData) }),
Factory: ({ model, calculatedModel }) => {
- const finalStyle = useMemo(() => !model.enableStyleOnReadonly && model.readOnly ? {
- ...model.allStyles.fontStyles,
- ...model.allStyles.dimensionsStyles,
- } : model.allStyles.fullStyle, [model.enableStyleOnReadonly, model.readOnly, model.allStyles]);
-
-
return (
{(value, onChange) => {
@@ -41,7 +36,7 @@ const SwitchComponent: SwitchComponentDefinition = {
,
dataTypeSupported: ({ dataType, dataFormat }) =>
dataType === DataTypes.string && dataFormat === StringFormats.multiline,
@@ -115,6 +116,9 @@ const TextAreaComponent: TextAreaComponentDefinition = {
color: maxLength && currentLength > maxLength ? '#ff4d4f' : '#8c8c8c',
marginTop: '0px',
marginBottom: '0px',
+ width: model.allStyles.dimensionsStyles?.width,
+ minWidth: model.allStyles.dimensionsStyles?.minWidth,
+ maxWidth: model.allStyles.dimensionsStyles?.maxWidth,
}}
>
{currentLength}
diff --git a/shesha-reactjs/src/designer-components/textField/textField.tsx b/shesha-reactjs/src/designer-components/textField/textField.tsx
index d18fbdc87b..4b66ff8568 100644
--- a/shesha-reactjs/src/designer-components/textField/textField.tsx
+++ b/shesha-reactjs/src/designer-components/textField/textField.tsx
@@ -25,6 +25,7 @@ const TextFieldComponent: TextFieldComponentDefinition = {
canBeJsSetting: true,
name: 'Text field',
icon: ,
+ preserveDimensionsInDesigner: true,
dataTypeSupported: ({ dataType, dataFormat }) =>
dataType === DataTypes.string &&
(!dataFormat ||
diff --git a/shesha-reactjs/src/designer-components/timeField/index.tsx b/shesha-reactjs/src/designer-components/timeField/index.tsx
index 3ef797c5a6..4a9dfc0584 100644
--- a/shesha-reactjs/src/designer-components/timeField/index.tsx
+++ b/shesha-reactjs/src/designer-components/timeField/index.tsx
@@ -24,6 +24,7 @@ export const TimeFieldComponent: TimeFieldComponentDefinition = {
isOutput: true,
canBeJsSetting: true,
icon: ,
+ preserveDimensionsInDesigner: true,
dataTypeSupported: ({ dataType }) => dataType === DataTypes.time,
calculateModel: (model, allData) => ({
eventHandlers: getAllEventHandlers(model, allData),
diff --git a/shesha-reactjs/src/hooks/formComponentHooks.ts b/shesha-reactjs/src/hooks/formComponentHooks.ts
index 47b253881b..6a3d6730c0 100644
--- a/shesha-reactjs/src/hooks/formComponentHooks.ts
+++ b/shesha-reactjs/src/hooks/formComponentHooks.ts
@@ -196,11 +196,20 @@ export function useActualContextExecutionExecutor(
model: TModel & IStyleType & Omit,
+ options?: IUseFormComponentStylesOptions,
): IFormComponentStyles => {
const app = useSheshaApplication();
- const jsStyle = useActualContextExecution(model.style, undefined, {}); // use default style if empty or error
+ const { useWrapperStyle } = options || {};
+ // For container components, use wrapperStyle instead of style
+ const styleSource = useWrapperStyle && model.wrapperStyle ? (model).wrapperStyle : model.style;
+ const jsStyle = useActualContextExecution(styleSource, undefined, {}); // use default style if empty or error
const { designerWidth } = useCanvas();
const { dimensions, border, font, shadow, background, stylingBox, overflow } = model;
@@ -218,11 +227,11 @@ export const useFormComponentStyles = (
const stylingBoxParsed = useMemo(() => jsonSafeParse(stylingBox || '{}') ?? {}, [stylingBox]);
- const dimensionsStyles = useMemo(() => getDimensionsStyle(dimensions, stylingBoxParsed, designerWidth), [dimensions, stylingBoxParsed, designerWidth]);
const borderStyles = useMemo(() => getBorderStyle(border, jsStyle), [border, jsStyle]);
const fontStyles = useMemo(() => getFontStyle(font), [font]);
const shadowStyles = useMemo(() => getShadowStyle(shadow), [shadow]);
const stylingBoxAsCSS = useMemo(() => pickStyleFromModel(stylingBoxParsed), [stylingBoxParsed]);
+ const dimensionsStyles = useMemo(() => getDimensionsStyle(dimensions, designerWidth, undefined), [dimensions, designerWidth]);
const overflowStyles = useMemo(() => overflow ? getOverflowStyle(overflow, false) : {}, [overflow]);
useDeepCompareEffect(() => {
@@ -256,6 +265,14 @@ export const useFormComponentStyles = (
const fullStyle = useDeepCompareMemo(() => ({ ...appearanceStyle, ...jsStyle }), [appearanceStyle, jsStyle]);
+ // Extract margin styles for wrapper use
+ const margins = useMemo(() => ({
+ marginTop: fullStyle.marginTop,
+ marginBottom: fullStyle.marginBottom,
+ marginLeft: fullStyle.marginLeft,
+ marginRight: fullStyle.marginRight,
+ }), [fullStyle.marginTop, fullStyle.marginBottom, fullStyle.marginLeft, fullStyle.marginRight]);
+
const allStyles: IFormComponentStyles = useMemo(() => ({
stylingBoxAsCSS,
dimensionsStyles,
@@ -267,7 +284,8 @@ export const useFormComponentStyles = (
jsStyle,
appearanceStyle,
fullStyle,
- }), [stylingBoxAsCSS, dimensionsStyles, borderStyles, fontStyles, backgroundStyles, shadowStyles, overflowStyles, jsStyle, appearanceStyle, fullStyle]);
+ margins,
+ }), [stylingBoxAsCSS, dimensionsStyles, borderStyles, fontStyles, backgroundStyles, shadowStyles, overflowStyles, jsStyle, appearanceStyle, fullStyle, margins]);
return allStyles;
};
diff --git a/shesha-reactjs/src/interfaces/formDesigner.ts b/shesha-reactjs/src/interfaces/formDesigner.ts
index f002b5377e..821d3d3f3c 100644
--- a/shesha-reactjs/src/interfaces/formDesigner.ts
+++ b/shesha-reactjs/src/interfaces/formDesigner.ts
@@ -12,7 +12,7 @@ import {
} from '@/providers/form/models';
import { Migrator, MigratorFluent } from '@/utils/fluentMigrator/migrator';
import { IModelMetadata, IPropertyMetadata } from './metadata';
-import { IAjaxResponseBase, IApplicationContext, IErrorInfo } from '..';
+import { IAjaxResponseBase, IApplicationContext, IDimensionsValue, IErrorInfo } from '..';
import { ISheshaApplicationInstance } from '@/providers/sheshaApplication/application';
import { AxiosResponse } from 'axios';
import { FormBuilderFactory } from '@/form-factory/interfaces';
@@ -187,6 +187,36 @@ export type IToolboxComponent boolean;
editorAdapter?: IEditorAdapter;
+
+ /**
+ * Controls dimension preservation in designer mode.
+ * - `true`: Preserve all original dimensions (width, height, min/max)
+ * - `false` or `undefined`: Fill 100% of wrapper (default behavior)
+ * - Array of dimension names: Preserve only specified dimensions (e.g., ['height'] preserves only height)
+ *
+ * Use this for components that need to preserve specific dimensions in designer mode,
+ * such as textArea which typically needs to preserve height while filling width.
+ *
+ * @example
+ * ```typescript
+ * preserveDimensionsInDesigner: true, // Preserve all dimensions
+ * preserveDimensionsInDesigner: ['height'], // Preserve only height
+ * preserveDimensionsInDesigner: ['width', 'height'], // Preserve width and height
+ * ```
+ */
+ preserveDimensionsInDesigner?: boolean | Array<'width' | 'height' | 'minWidth' | 'maxWidth' | 'minHeight' | 'maxHeight'>;
+ /**
+ * Optional function to customize how component dimensions are calculated in designer mode.
+ * This allows components to define their own sizing behavior instead of relying on generic logic.
+ *
+ * @param originalDims - The original dimensions from the component model
+ * @param deviceDims - The default device dimensions (usually 100% width/height)
+ * @returns The calculated dimensions for designer mode, or undefined to use default behavior
+ */
+ getDesignerDimensions?: (
+ originalDims: IDimensionsValue | undefined,
+ deviceDims: IDimensionsValue | undefined,
+ ) => IDimensionsValue | undefined;
} & ToolboxComponentAsTemplate;
export type ComponentDefinition =
diff --git a/shesha-reactjs/src/providers/canvas/contexts.ts b/shesha-reactjs/src/providers/canvas/contexts.ts
index 1d6c8c404f..7e9125397d 100644
--- a/shesha-reactjs/src/providers/canvas/contexts.ts
+++ b/shesha-reactjs/src/providers/canvas/contexts.ts
@@ -17,7 +17,7 @@ export interface ICanvasStateContext {
export interface ICanvasWidthProps {
width: number | string;
- deviceType: string;
+ deviceType: IDeviceTypes;
}
export interface ICanvasActionsContext {
setDesignerDevice: (deviceType: IDeviceTypes) => void;
diff --git a/shesha-reactjs/src/providers/canvas/index.tsx b/shesha-reactjs/src/providers/canvas/index.tsx
index df5afbb7d8..8a23981ace 100644
--- a/shesha-reactjs/src/providers/canvas/index.tsx
+++ b/shesha-reactjs/src/providers/canvas/index.tsx
@@ -46,8 +46,8 @@ const CanvasProvider: FC = ({
dispatch(setDesignerDeviceAction(deviceType));
}, []);
- const setCanvasWidth = useCallback((width: number, deviceType: string) => {
- dispatch(setCanvasWidthAction({ width, deviceType }));
+ const setCanvasWidth = useCallback((width: number | string, deviceType: IDeviceTypes) => {
+ dispatch(setCanvasWidthAction({ width: typeof width === 'string' ? width : `${width}px`, deviceType }));
}, []);
const setCanvasZoom = useCallback((zoom: number) => {
diff --git a/shesha-reactjs/src/providers/canvas/reducer.ts b/shesha-reactjs/src/providers/canvas/reducer.ts
index 9519802ee8..a1118b93ed 100644
--- a/shesha-reactjs/src/providers/canvas/reducer.ts
+++ b/shesha-reactjs/src/providers/canvas/reducer.ts
@@ -30,7 +30,8 @@ export default handleActions(
return {
...state,
designerWidth: typeof width === 'string' ? width : `${width}px`,
- designerDevice: deviceType as IDeviceTypes,
+ designerDevice: deviceType,
+ activeDevice: getSmallerDevice(deviceType, state.physicalDevice),
};
},
[CanvasConfigActionEnums.SetCanvasZoom]: (state: ICanvasStateContext, action: ReduxActions.Action) => {
diff --git a/shesha-reactjs/src/providers/canvas/utils.ts b/shesha-reactjs/src/providers/canvas/utils.ts
index b6a6a7488a..5cdba478ab 100644
--- a/shesha-reactjs/src/providers/canvas/utils.ts
+++ b/shesha-reactjs/src/providers/canvas/utils.ts
@@ -35,24 +35,35 @@ export const getSmallerDevice = (a: IDeviceTypes, b: IDeviceTypes): IDeviceTypes
};
-export function widthRelativeToCanvas(width: string | number, canvasWidth: string = '100vw'): string {
- if (typeof width === 'number') {
- return `${width}px`;
+/**
+ * Converts viewport units (vw/vh) to be relative to a specific canvas dimension
+ * @param dimension - The dimension value (e.g., "50vw", "100vh", "100px", 300)
+ * @param canvasDimension - The canvas dimension to calculate relative to (e.g., '100vw', '1024px')
+ * @param unit - The unit type to convert ('vw' or 'vh')
+ * @returns The converted dimension string
+ */
+export const dimensionRelativeToCanvas = (
+ dimension: string | number,
+ canvasDimension: string,
+ unit: 'vw' | 'vh',
+): string => {
+ if (typeof dimension === 'number') {
+ return `${dimension}px`;
}
- const trimmed = String(width).trim();
- const vwRegex = /^([\d.]+)\s*vw$/i;
- const vwMatch = vwRegex.exec(trimmed);
+ const trimmed = String(dimension).trim();
+ const unitRegex = new RegExp(`^([\\d.]+)\\s*${unit}$`, 'i');
+ const unitMatch = unitRegex.exec(trimmed);
- if (vwMatch && vwMatch[1] !== undefined) {
- const percentageOfCanvas = parseFloat(vwMatch[1]);
+ if (unitMatch && unitMatch[1] !== undefined) {
+ const percentageOfCanvas = parseFloat(unitMatch[1]);
if (!Number.isNaN(percentageOfCanvas)) {
- return `calc((${percentageOfCanvas} * ${canvasWidth}) / 100)`;
+ return `calc((${percentageOfCanvas} * ${canvasDimension}) / 100)`;
}
}
return trimmed;
-}
+};
export const defaultDesignerWidth = `${(typeof window !== 'undefined' ? window.screen.availWidth : 1024)}px`;
diff --git a/shesha-reactjs/src/providers/form/models.ts b/shesha-reactjs/src/providers/form/models.ts
index a18ec577a2..453cce9253 100644
--- a/shesha-reactjs/src/providers/form/models.ts
+++ b/shesha-reactjs/src/providers/form/models.ts
@@ -74,6 +74,8 @@ export interface IStyleType {
secondaryTextColor?: ColorValueType;
overflow?: boolean | "dropdown" | "menu" | "scroll";
hideScrollBar?: boolean;
+ autoWidth?: boolean;
+ autoHeight?: boolean;
}
export interface IInputStyles extends IStyleType {
@@ -197,6 +199,8 @@ export interface IFormComponentStyles {
appearanceStyle: CSSProperties;
/** Styles assempled from {...appearanceStyle, ...jsStyle} */
fullStyle: CSSProperties;
+ /** Margin styles extracted from fullStyle for wrapper use */
+ margins: CSSProperties;
}
/**
@@ -209,7 +213,8 @@ export interface IConfigurableFormComponent
IComponentLabelProps,
IComponentVisibilityProps,
IComponentRuntimeProps,
- IComponentMetadata {
+ IComponentMetadata,
+ IStyleType {
/** Type of the component */
type: string;
@@ -249,6 +254,8 @@ export interface IConfigurableFormComponent
/** Default css style applied as string */
stylingBox?: string;
+ wrapperStyle?: string;
+
noDataText?: string;
noDataIcon?: string;
@@ -274,6 +281,7 @@ export interface IConfigurableFormComponent
enableStyleOnReadonly?: boolean;
listType?: 'text' | 'thumbnail';
+
}
export const isConfigurableFormComponent = (component: unknown): component is IConfigurableFormComponent =>
@@ -502,3 +510,12 @@ export type GenericDictionary = { [key: string]: any };
export const STYLE_BOX_CSS_POPERTIES = ['marginTop', 'marginRight', 'marginBottom', 'marginLeft', 'paddingTop', 'paddingRight', 'paddingBottom', 'paddingLeft'] as const;
export type StyleBoxCssProperties = typeof STYLE_BOX_CSS_POPERTIES[number];
export type StyleBoxValue = Pick;
+
+export interface IContainerConfig {
+ dimensions?: IDimensionsValue;
+ stylingBox?: string;
+ style?: string;
+}
+export interface IComponentModelProps extends IConfigurableFormComponent {
+ container?: IContainerConfig;
+}
diff --git a/shesha-reactjs/src/providers/form/utils.ts b/shesha-reactjs/src/providers/form/utils.ts
index 93dee8b6c0..8feaaeb6ff 100644
--- a/shesha-reactjs/src/providers/form/utils.ts
+++ b/shesha-reactjs/src/providers/form/utils.ts
@@ -1219,10 +1219,22 @@ export const getStyle = (
formData: any = {},
globalState: any = {},
defaultStyle: object = emptyStyle,
+ excludeMargin: boolean = false,
): CSSProperties => {
if (!style) return defaultStyle;
// tslint:disable-next-line:function-constructor
- return new Function('data, globalState', style)(formData, globalState);
+ const allStyle = new Function('data, globalState', style)(formData, globalState);
+ if (!allStyle || typeof allStyle !== 'object') return defaultStyle;
+ const { margin, marginTop, marginBottom, marginLeft, marginRight, ...rest } = allStyle;
+ return excludeMargin
+ ? rest : {
+ margin,
+ marginTop,
+ marginBottom,
+ marginLeft,
+ marginRight,
+ ...rest,
+ };
};
export const getLayoutStyle = (model: IConfigurableFormComponent, args: { [key: string]: any }): CSSProperties => {
diff --git a/shesha-reactjs/src/utils/style.ts b/shesha-reactjs/src/utils/style.ts
index 0fe91da39d..d6d3f74f72 100644
--- a/shesha-reactjs/src/utils/style.ts
+++ b/shesha-reactjs/src/utils/style.ts
@@ -1,12 +1,108 @@
import React from 'react';
import { isDefined } from "@/utils/nullables";
+import { executeScriptSync } from '@/providers/form/utils';
+import { IPropertySetting } from '..';
-export const addPx = (value: number | string | null | undefined): string | undefined => {
- return !isDefined(value)
- ? undefined
- : typeof value === 'number' || (typeof value === 'string' && /^\d+(\.\d+)?$/.test(value))
- ? `${value}px`
- : value;
+export interface DimensionValue {
+ value: number;
+ unit: 'px' | '%' | 'vw' | 'vh' | 'em' | 'rem' | 'auto' | 'none';
+}
+
+/**
+ * Type guard to check if a value is a valid dimension input type
+ * @param value - The value to check
+ * @returns true if the value is string, number, null, or undefined
+ */
+const isValidDimensionResult = (value: unknown): value is string | number | null | undefined => {
+ const valueType = typeof value;
+ return valueType === 'string' || valueType === 'number' || value === null || value === undefined;
+};
+
+/**
+ * Parse a dimension value into its numeric value and unit
+ * @param value - The dimension value to parse (e.g., "50vw", "100%", 300, "auto", or JS code object)
+ * @param context - Optional context object containing available constants (from useAvailableConstantsData)
+ * @returns Parsed dimension object with value and unit, or null if invalid
+ */
+export const parseDimension = (value: string | number | null | undefined | IPropertySetting, context?: object): DimensionValue | null => {
+ if (!isDefined(value)) return null;
+
+ if (typeof value === 'number') {
+ return { value, unit: 'px' };
+ }
+
+ // Handle JavaScript code execution for dynamic values
+ if (typeof value === 'object' && value?._mode === 'code' && value?._code) {
+ try {
+ const executedValue = executeScriptSync(value._code, context ?? {});
+
+ // Validate that the executed result is a valid dimension type
+ if (!isValidDimensionResult(executedValue)) {
+ console.error(
+ `Invalid dimension value returned from script execution. Expected string, number, null, or undefined but got ${typeof executedValue}:`,
+ executedValue,
+ );
+ return null;
+ }
+
+ // Recursively parse the validated result
+ return parseDimension(executedValue, context);
+ } catch (error) {
+ console.error('Error executing dimension code:', error);
+ return null;
+ }
+ }
+ // Handle IPropertySetting with _mode === 'value' - extract the underlying value
+ if (typeof value === 'object' && value?._mode === 'value' && isDefined(value?._value)) {
+ // Validate that _value is a valid dimension type before recursing
+ if (!isValidDimensionResult(value._value)) {
+ console.error(
+ `Invalid dimension value in IPropertySetting._value. Expected string, number, null, or undefined but got ${typeof value._value}:`,
+ value._value,
+ );
+ return null;
+ }
+ return parseDimension(value._value, context);
+ }
+
+ if (typeof value !== 'string') return null;
+
+ if (value === 'auto' || value === 'none') {
+ return { value: 0, unit: value };
+ }
+
+ // Match number with optional unit
+ const match = /^(-?\d+(?:\.\d+)?)(px|%|vw|vh|em|rem)?$/.exec(value.trim());
+ if (match && match[1] !== undefined) {
+ const unit = match[2] || 'px';
+
+ // Type guard: validate unit is one of the allowed values
+ if (unit === 'px' || unit === '%' || unit === 'vw' || unit === 'vh' || unit === 'em' || unit === 'rem') {
+ return {
+ value: parseFloat(match[1]),
+ unit,
+ };
+ }
+ }
+
+ return null;
+};
+
+/**
+ * Add 'px' unit to bare numbers, preserve existing units
+ * @param value - The value to add units to
+ * @param context - Optional context object containing available constants (from useAvailableConstantsData)
+ * @returns String with appropriate units, or undefined
+ */
+export const addPx = (value: number | string | null | undefined, context?: object): string | undefined => {
+ const parsed = parseDimension(value, context);
+ if (!parsed) return undefined;
+
+ if (parsed.unit === 'auto' || parsed.unit === 'none') {
+ return parsed.unit;
+ }
+
+ return `${parsed.value}${parsed.unit}`;
};
/**