147 lines
3.5 KiB
TypeScript
147 lines
3.5 KiB
TypeScript
/**
|
|
* Element Styles
|
|
*
|
|
* Unified types and utilities for UI element CSS styling.
|
|
* Used by constructor, RuntimePresentation, and element-type-defaults admin pages.
|
|
*/
|
|
|
|
import type { CSSProperties } from 'react';
|
|
|
|
/**
|
|
* CSS style properties supported by UI elements.
|
|
* These properties can be set in element-type-defaults and applied at runtime.
|
|
*/
|
|
export interface ElementStyleProperties {
|
|
width?: string;
|
|
height?: string;
|
|
minWidth?: string;
|
|
maxWidth?: string;
|
|
minHeight?: string;
|
|
maxHeight?: string;
|
|
margin?: string;
|
|
padding?: string;
|
|
gap?: string;
|
|
fontSize?: string;
|
|
lineHeight?: string;
|
|
fontWeight?: string;
|
|
border?: string;
|
|
borderRadius?: string;
|
|
opacity?: string;
|
|
boxShadow?: string;
|
|
display?: string;
|
|
position?: string;
|
|
justifyContent?: string;
|
|
alignItems?: string;
|
|
textAlign?: string;
|
|
zIndex?: string;
|
|
backgroundColor?: string;
|
|
color?: string;
|
|
fontFamily?: string;
|
|
fontStretch?: string;
|
|
}
|
|
|
|
/**
|
|
* Array of CSS property names for iteration.
|
|
* Used when applying styles from element data to CSSProperties.
|
|
*/
|
|
export const ELEMENT_STYLE_PROPS = [
|
|
'width',
|
|
'height',
|
|
'minWidth',
|
|
'maxWidth',
|
|
'minHeight',
|
|
'maxHeight',
|
|
'margin',
|
|
'padding',
|
|
'gap',
|
|
'fontSize',
|
|
'lineHeight',
|
|
'fontWeight',
|
|
'border',
|
|
'borderRadius',
|
|
'boxShadow',
|
|
'display',
|
|
'position',
|
|
'justifyContent',
|
|
'alignItems',
|
|
'textAlign',
|
|
'zIndex',
|
|
'backgroundColor',
|
|
'color',
|
|
'fontFamily',
|
|
'fontStretch',
|
|
] as const;
|
|
|
|
/**
|
|
* Properties that need numeric conversion
|
|
*/
|
|
const NUMERIC_PROPS = ['opacity', 'zIndex'] as const;
|
|
|
|
/**
|
|
* Get trimmed CSS value from unknown input.
|
|
* Returns empty string for null/undefined, but preserves '0' for explicit zero values.
|
|
*/
|
|
const getTrimmedValue = (value: unknown): string => {
|
|
if (value === null || value === undefined) return '';
|
|
// Preserve 0 as '0' - explicit zero should be applied
|
|
if (value === 0) return '0';
|
|
return String(value).trim();
|
|
};
|
|
|
|
/**
|
|
* Build React CSSProperties from element style properties.
|
|
* Handles type coercion for special properties like opacity and zIndex.
|
|
*
|
|
* @param element - Object containing style properties
|
|
* @returns React CSSProperties object
|
|
*
|
|
* @example
|
|
* const style = buildElementStyle({
|
|
* width: '100px',
|
|
* opacity: '0.5',
|
|
* display: 'flex',
|
|
* });
|
|
*/
|
|
export function buildElementStyle(
|
|
element: Partial<ElementStyleProperties>,
|
|
): CSSProperties {
|
|
const style: CSSProperties = {};
|
|
const source = element as Record<string, unknown>;
|
|
|
|
// Apply string properties
|
|
ELEMENT_STYLE_PROPS.forEach((prop) => {
|
|
const value = getTrimmedValue(source[prop]);
|
|
if (value) {
|
|
(style as Record<string, unknown>)[prop] = value;
|
|
}
|
|
});
|
|
|
|
// Handle opacity (needs numeric conversion)
|
|
const opacityValue = getTrimmedValue(source.opacity);
|
|
if (opacityValue) {
|
|
const parsed = Number(opacityValue);
|
|
if (Number.isFinite(parsed)) {
|
|
style.opacity = parsed;
|
|
}
|
|
}
|
|
|
|
// Handle zIndex (needs numeric conversion, already in style from loop but override with number)
|
|
const zIndexValue = getTrimmedValue(source.zIndex);
|
|
if (zIndexValue) {
|
|
const parsed = Number(zIndexValue);
|
|
if (Number.isFinite(parsed)) {
|
|
style.zIndex = parsed;
|
|
}
|
|
}
|
|
|
|
return style;
|
|
}
|
|
|
|
/**
|
|
* All style property names including numeric ones.
|
|
* Used for form state management in element-type-defaults admin.
|
|
*/
|
|
export const ALL_STYLE_PROPS = [...ELEMENT_STYLE_PROPS, 'opacity'] as const;
|
|
|
|
export type StylePropName = (typeof ALL_STYLE_PROPS)[number];
|