fixed css units inconsistence
This commit is contained in:
parent
8ef30576b1
commit
4e431eab9b
File diff suppressed because one or more lines are too long
@ -286,10 +286,42 @@ export const toOptionalTrimmed = (value: string): string | undefined => {
|
||||
return normalized ? normalized : undefined;
|
||||
};
|
||||
|
||||
/**
|
||||
* Convert a value to a CSS value with unit.
|
||||
* Handles edge cases:
|
||||
* - Complex values with spaces (e.g., "10px 20px") → preserved as-is
|
||||
* - Values with existing units (e.g., "100vw", "16px") → preserved as-is
|
||||
* - Plain numbers (e.g., "24", 100) → append unit
|
||||
* - Zero values ("0", 0) → return "0" (no unit needed)
|
||||
* - CSS functions (e.g., "calc(...)") → preserved as-is
|
||||
*/
|
||||
export const toUnitValue = (
|
||||
value: string,
|
||||
unit: 'vw' | 'vh' | 'px',
|
||||
unit: 'vw' | 'vh' | 'px' | 'rem',
|
||||
): string | undefined => {
|
||||
const normalized = normalizeNumberString(value);
|
||||
return normalized ? `${normalized}${unit}` : undefined;
|
||||
const trimmed = String(value ?? '').trim();
|
||||
if (!trimmed) return undefined;
|
||||
|
||||
// Zero doesn't need a unit
|
||||
if (trimmed === '0') return '0';
|
||||
|
||||
// Complex values (contain spaces) - return as-is
|
||||
if (trimmed.includes(' ')) return trimmed;
|
||||
|
||||
// CSS functions (calc, var, etc.) - return as-is
|
||||
if (/^(calc|var|min|max|clamp)\(/i.test(trimmed)) return trimmed;
|
||||
|
||||
// Already has a unit (letters or %) at the end - return as-is
|
||||
if (/[a-z%]+$/i.test(trimmed)) return trimmed;
|
||||
|
||||
// Handle numbers (including negative and decimals like ".5")
|
||||
const num = parseFloat(trimmed);
|
||||
if (Number.isFinite(num)) {
|
||||
// Zero after parsing
|
||||
if (num === 0) return '0';
|
||||
return `${num}${unit}`;
|
||||
}
|
||||
|
||||
// Fallback: return as-is if non-empty
|
||||
return trimmed || undefined;
|
||||
};
|
||||
|
||||
@ -650,22 +650,31 @@ export function useElementSettingsForm(options: UseElementSettingsFormOptions) {
|
||||
}
|
||||
|
||||
// Description type settings
|
||||
// Note: Color/fontSize/fontWeight cascade from General Element Styles via CSS inheritance
|
||||
// Only set section-specific values if explicitly configured (allows inheritance)
|
||||
if (isDescriptionType) {
|
||||
settings.iconUrl = state.iconUrl.trim();
|
||||
settings.descriptionTitle = state.descriptionTitle.trim();
|
||||
settings.descriptionText = state.descriptionText;
|
||||
settings.descriptionTitleFontSize =
|
||||
state.descriptionTitleFontSize.trim() || '48px';
|
||||
settings.descriptionTextFontSize =
|
||||
state.descriptionTextFontSize.trim() || '36px';
|
||||
settings.descriptionTitleFontFamily =
|
||||
state.descriptionTitleFontFamily.trim() || 'inherit';
|
||||
settings.descriptionTextFontFamily =
|
||||
state.descriptionTextFontFamily.trim() || 'inherit';
|
||||
settings.descriptionTitleColor =
|
||||
state.descriptionTitleColor.trim() || '#000000';
|
||||
settings.descriptionTextColor =
|
||||
state.descriptionTextColor.trim() || '#4B5563';
|
||||
// Only include if explicitly set - allows CSS inheritance from wrapper
|
||||
if (state.descriptionTitleFontSize.trim()) {
|
||||
settings.descriptionTitleFontSize = state.descriptionTitleFontSize.trim();
|
||||
}
|
||||
if (state.descriptionTextFontSize.trim()) {
|
||||
settings.descriptionTextFontSize = state.descriptionTextFontSize.trim();
|
||||
}
|
||||
if (state.descriptionTitleFontFamily.trim()) {
|
||||
settings.descriptionTitleFontFamily = state.descriptionTitleFontFamily.trim();
|
||||
}
|
||||
if (state.descriptionTextFontFamily.trim()) {
|
||||
settings.descriptionTextFontFamily = state.descriptionTextFontFamily.trim();
|
||||
}
|
||||
if (state.descriptionTitleColor.trim()) {
|
||||
settings.descriptionTitleColor = state.descriptionTitleColor.trim();
|
||||
}
|
||||
if (state.descriptionTextColor.trim()) {
|
||||
settings.descriptionTextColor = state.descriptionTextColor.trim();
|
||||
}
|
||||
}
|
||||
|
||||
// Gallery type settings
|
||||
|
||||
@ -56,9 +56,10 @@ const NavigationElement: React.FC<NavigationElementProps> = ({
|
||||
}
|
||||
|
||||
// Without icon: render text label
|
||||
// fontSize/fontWeight/color inherit from General Element Styles via CSS cascade
|
||||
return (
|
||||
<div className={className} style={style}>
|
||||
<span className='px-4 py-2 text-sm' style={labelFontStyle}>
|
||||
<span className='px-4 py-2' style={labelFontStyle}>
|
||||
{element.navLabel ||
|
||||
(element.type === 'navigation_next' ? 'Next' : 'Back')}
|
||||
</span>
|
||||
|
||||
@ -21,9 +21,10 @@ const PopupElement: React.FC<PopupElementProps> = ({
|
||||
className,
|
||||
style,
|
||||
}) => {
|
||||
// fontSize/fontWeight/color inherit from General Element Styles via CSS cascade
|
||||
return (
|
||||
<div className={className} style={style}>
|
||||
<span className='px-4 py-2 text-sm'>{element.label || 'Popup'}</span>
|
||||
<span className='px-4 py-2'>{element.label || 'Popup'}</span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@ -9,6 +9,15 @@ import type { CSSProperties } from 'react';
|
||||
|
||||
/**
|
||||
* Normalize a numeric value to include a CSS unit suffix.
|
||||
* Handles edge cases:
|
||||
* - Complex values with spaces (e.g., "10px 20px") → preserved as-is
|
||||
* - Values with existing units (e.g., "100vw", "16px") → preserved as-is
|
||||
* - Plain numbers (e.g., "24", 100) → append unit
|
||||
* - Zero values ("0", 0) → return "0" (no unit needed)
|
||||
* - Negative values (e.g., "-10") → append unit
|
||||
* - Decimal without leading zero (e.g., ".5") → append unit
|
||||
* - CSS functions (e.g., "calc(...)") → preserved as-is
|
||||
*
|
||||
* @param value - The value to normalize
|
||||
* @param unit - The unit to append (e.g., 'px', 'vw', 'vh', 's')
|
||||
* @returns Normalized value with unit, or empty string if invalid
|
||||
@ -22,17 +31,27 @@ function normalizeWithUnit(
|
||||
const str = String(value).trim();
|
||||
if (!str) return '';
|
||||
|
||||
// Already has a unit - return as is
|
||||
if (/[a-z%]+$/i.test(str)) {
|
||||
return str;
|
||||
}
|
||||
// Zero doesn't need a unit
|
||||
if (str === '0') return '0';
|
||||
|
||||
// Just a number - append unit
|
||||
// Complex values (contain spaces) - return as-is
|
||||
if (str.includes(' ')) return str;
|
||||
|
||||
// CSS functions (calc, var, etc.) - return as-is
|
||||
if (/^(calc|var|min|max|clamp)\(/i.test(str)) return str;
|
||||
|
||||
// Already has a unit (letters or %) at the end - return as-is
|
||||
if (/[a-z%]+$/i.test(str)) return str;
|
||||
|
||||
// Handle numbers (including negative and decimals like ".5")
|
||||
const num = parseFloat(str);
|
||||
if (Number.isFinite(num)) {
|
||||
// Zero after parsing
|
||||
if (num === 0) return '0';
|
||||
return `${num}${unit}`;
|
||||
}
|
||||
|
||||
// Fallback: return as-is
|
||||
return str;
|
||||
}
|
||||
|
||||
@ -150,7 +169,8 @@ export function buildElementStyle(
|
||||
// Properties that need viewport height unit (vh)
|
||||
const vhProps = ['height', 'minHeight', 'maxHeight'];
|
||||
// Properties that need pixel unit (px)
|
||||
const pxProps = ['border', 'borderRadius'];
|
||||
// Note: 'border' is NOT included - it's a complex value like "1px solid #ccc"
|
||||
const pxProps = ['borderRadius'];
|
||||
|
||||
// Apply string properties with unit normalization where needed
|
||||
ELEMENT_STYLE_PROPS.forEach((prop) => {
|
||||
|
||||
@ -61,15 +61,65 @@ const getTrimmedValue = (value: unknown): string => {
|
||||
};
|
||||
|
||||
/**
|
||||
* Apply value with default fallback
|
||||
* Normalize a numeric value to include a CSS unit suffix.
|
||||
* Handles edge cases:
|
||||
* - Complex values with spaces (e.g., "0.5rem 0.75rem") → preserved as-is
|
||||
* - Values with existing units (e.g., "1.5rem", "16px") → preserved as-is
|
||||
* - Plain numbers (e.g., "0.875", 0.5) → append unit
|
||||
* - Zero values ("0", 0) → return "0" (no unit needed)
|
||||
* - Negative values (e.g., "-0.5") → append unit
|
||||
* - Decimal without leading zero (e.g., ".5") → append unit
|
||||
* - CSS functions (e.g., "calc(...)") → preserved as-is
|
||||
*
|
||||
* @param value - The value to normalize
|
||||
* @param unit - The unit to append (e.g., 'rem', 'px')
|
||||
* @returns Normalized value with unit, or empty string if invalid
|
||||
*/
|
||||
const normalizeWithUnit = (value: unknown, unit: string): string => {
|
||||
const trimmed = getTrimmedValue(value);
|
||||
if (!trimmed) return '';
|
||||
|
||||
// Zero doesn't need a unit
|
||||
if (trimmed === '0') return '0';
|
||||
|
||||
// Complex values (contain spaces) - return as-is (e.g., "0.5rem 0.75rem")
|
||||
if (trimmed.includes(' ')) return trimmed;
|
||||
|
||||
// CSS functions (calc, var, etc.) - return as-is
|
||||
if (/^(calc|var|min|max|clamp)\(/i.test(trimmed)) return trimmed;
|
||||
|
||||
// Already has a unit (letters or %) at the end - return as-is
|
||||
if (/[a-z%]+$/i.test(trimmed)) return trimmed;
|
||||
|
||||
// Handle numbers (including negative and decimals like ".5")
|
||||
const num = parseFloat(trimmed);
|
||||
if (Number.isFinite(num)) {
|
||||
// Zero after parsing
|
||||
if (num === 0) return '0';
|
||||
return `${num}${unit}`;
|
||||
}
|
||||
|
||||
// Fallback: return as-is (shouldn't normally reach here)
|
||||
return trimmed;
|
||||
};
|
||||
|
||||
/** Normalize rem values (fontSize, padding, borderRadius, gap) */
|
||||
const normalizeRemValue = (value: unknown): string => normalizeWithUnit(value, 'rem');
|
||||
|
||||
/** Normalize pixel values (for properties that use px) */
|
||||
const normalizePxValue = (value: unknown): string => normalizeWithUnit(value, 'px');
|
||||
|
||||
/**
|
||||
* Apply value with default fallback and optional unit normalization
|
||||
*/
|
||||
const applyWithDefault = (
|
||||
style: CSSProperties,
|
||||
prop: keyof CSSProperties,
|
||||
value: unknown,
|
||||
defaultValue: unknown,
|
||||
normalize?: (v: unknown) => string,
|
||||
): void => {
|
||||
const trimmed = getTrimmedValue(value);
|
||||
const trimmed = normalize ? normalize(value) : getTrimmedValue(value);
|
||||
if (trimmed) {
|
||||
(style as Record<string, unknown>)[prop] = trimmed;
|
||||
} else if (defaultValue !== undefined) {
|
||||
@ -79,13 +129,15 @@ const applyWithDefault = (
|
||||
|
||||
/**
|
||||
* Apply value only if explicitly set (no default - allows CSS inheritance)
|
||||
* with optional unit normalization
|
||||
*/
|
||||
const applyIfSet = (
|
||||
style: CSSProperties,
|
||||
prop: keyof CSSProperties,
|
||||
value: unknown,
|
||||
normalize?: (v: unknown) => string,
|
||||
): void => {
|
||||
const trimmed = getTrimmedValue(value);
|
||||
const trimmed = normalize ? normalize(value) : getTrimmedValue(value);
|
||||
if (trimmed) {
|
||||
(style as Record<string, unknown>)[prop] = trimmed;
|
||||
}
|
||||
@ -103,12 +155,12 @@ export function buildGalleryHeaderStyle(
|
||||
applyIfSet(style, 'backgroundColor', element.galleryHeaderBackgroundColor);
|
||||
// Inheritable properties: only apply if explicitly set (allows CSS inheritance from wrapper)
|
||||
applyIfSet(style, 'color', element.galleryHeaderColor);
|
||||
applyIfSet(style, 'fontSize', element.galleryHeaderFontSize);
|
||||
applyIfSet(style, 'fontSize', element.galleryHeaderFontSize, normalizeRemValue);
|
||||
applyIfSet(style, 'fontWeight', element.galleryHeaderFontWeight);
|
||||
// Non-inheritable properties: use defaults
|
||||
applyWithDefault(style, 'padding', element.galleryHeaderPadding, defaults.padding);
|
||||
applyIfSet(style, 'borderRadius', element.galleryHeaderBorderRadius);
|
||||
applyIfSet(style, 'border', element.galleryHeaderBorder);
|
||||
applyWithDefault(style, 'padding', element.galleryHeaderPadding, defaults.padding, normalizeRemValue);
|
||||
applyIfSet(style, 'borderRadius', element.galleryHeaderBorderRadius, normalizeRemValue);
|
||||
applyIfSet(style, 'border', element.galleryHeaderBorder); // Complex value, no normalization
|
||||
|
||||
// Apply font family with font library resolution
|
||||
const fontKey = element.galleryHeaderFontFamily;
|
||||
@ -136,12 +188,12 @@ export function buildGalleryTitleStyle(
|
||||
applyWithDefault(style, 'backgroundColor', element.galleryTitleBackgroundColor, defaults.backgroundColor);
|
||||
// Inheritable properties: only apply if explicitly set (allows CSS inheritance from wrapper)
|
||||
applyIfSet(style, 'color', element.galleryTitleColor);
|
||||
applyIfSet(style, 'fontSize', element.galleryTitleFontSize);
|
||||
applyIfSet(style, 'fontSize', element.galleryTitleFontSize, normalizeRemValue);
|
||||
applyIfSet(style, 'fontWeight', element.galleryTitleFontWeight);
|
||||
// Non-inheritable properties: use defaults
|
||||
applyWithDefault(style, 'padding', element.galleryTitlePadding, defaults.padding);
|
||||
applyWithDefault(style, 'borderRadius', element.galleryTitleBorderRadius, defaults.borderRadius);
|
||||
applyIfSet(style, 'border', element.galleryTitleBorder);
|
||||
applyWithDefault(style, 'padding', element.galleryTitlePadding, defaults.padding, normalizeRemValue);
|
||||
applyWithDefault(style, 'borderRadius', element.galleryTitleBorderRadius, defaults.borderRadius, normalizeRemValue);
|
||||
applyIfSet(style, 'border', element.galleryTitleBorder); // Complex value, no normalization
|
||||
|
||||
// Apply font family with font library resolution
|
||||
const fontKey = element.galleryTitleFontFamily;
|
||||
@ -169,12 +221,12 @@ export function buildGallerySpanStyle(
|
||||
applyWithDefault(style, 'backgroundColor', element.gallerySpanBackgroundColor, defaults.backgroundColor);
|
||||
// Inheritable properties: only apply if explicitly set (allows CSS inheritance from wrapper)
|
||||
applyIfSet(style, 'color', element.gallerySpanColor);
|
||||
applyIfSet(style, 'fontSize', element.gallerySpanFontSize);
|
||||
applyIfSet(style, 'fontSize', element.gallerySpanFontSize, normalizeRemValue);
|
||||
applyIfSet(style, 'fontWeight', element.gallerySpanFontWeight);
|
||||
// Non-inheritable properties: use defaults
|
||||
applyWithDefault(style, 'padding', element.gallerySpanPadding, defaults.padding);
|
||||
applyWithDefault(style, 'borderRadius', element.gallerySpanBorderRadius, defaults.borderRadius);
|
||||
applyIfSet(style, 'border', element.gallerySpanBorder);
|
||||
applyWithDefault(style, 'padding', element.gallerySpanPadding, defaults.padding, normalizeRemValue);
|
||||
applyWithDefault(style, 'borderRadius', element.gallerySpanBorderRadius, defaults.borderRadius, normalizeRemValue);
|
||||
applyIfSet(style, 'border', element.gallerySpanBorder); // Complex value, no normalization
|
||||
|
||||
// Apply font family with font library resolution (fallback to galleryTextFontFamily for legacy support)
|
||||
const fontKey = element.gallerySpanFontFamily || element.galleryTextFontFamily;
|
||||
@ -197,7 +249,7 @@ export function buildGallerySpanGridStyle(
|
||||
element: Partial<CanvasElement>,
|
||||
): CSSProperties {
|
||||
const columns = getGalleryGridColumns(element, 'span');
|
||||
const gap = getTrimmedValue(element.gallerySpanGap) || '0.5rem';
|
||||
const gap = normalizeRemValue(element.gallerySpanGap) || '0.5rem';
|
||||
|
||||
return {
|
||||
display: 'grid',
|
||||
@ -216,8 +268,8 @@ export function buildGalleryCardStyle(
|
||||
const style: CSSProperties = {};
|
||||
|
||||
applyWithDefault(style, 'backgroundColor', element.galleryCardBackgroundColor, undefined);
|
||||
applyWithDefault(style, 'borderRadius', element.galleryCardBorderRadius, defaults.borderRadius);
|
||||
applyWithDefault(style, 'border', element.galleryCardBorder, undefined);
|
||||
applyWithDefault(style, 'borderRadius', element.galleryCardBorderRadius, defaults.borderRadius, normalizeRemValue);
|
||||
applyIfSet(style, 'border', element.galleryCardBorder); // Complex value, no normalization
|
||||
|
||||
return style;
|
||||
}
|
||||
@ -233,14 +285,14 @@ export function buildGalleryCardTitleStyle(
|
||||
// Inheritable properties: only apply if explicitly set (allows CSS inheritance from wrapper)
|
||||
// Note: card titles typically need white text for visibility over images
|
||||
applyIfSet(style, 'color', element.galleryCardTitleColor);
|
||||
applyIfSet(style, 'fontSize', element.galleryCardTitleFontSize);
|
||||
applyIfSet(style, 'fontSize', element.galleryCardTitleFontSize, normalizeRemValue);
|
||||
applyIfSet(style, 'fontWeight', element.galleryCardTitleFontWeight);
|
||||
|
||||
if (element.galleryCardTitleBackgroundColor) {
|
||||
style.backgroundColor = element.galleryCardTitleBackgroundColor;
|
||||
}
|
||||
|
||||
// Handle text shadow
|
||||
// Handle text shadow - complex value, no normalization
|
||||
const shadow = element.galleryCardTitleShadow;
|
||||
if (shadow) {
|
||||
style.textShadow = shadow;
|
||||
@ -270,7 +322,7 @@ export function buildGalleryCardGridStyle(
|
||||
element: Partial<CanvasElement>,
|
||||
): CSSProperties {
|
||||
const columns = getGalleryGridColumns(element, 'card');
|
||||
const gap = getTrimmedValue(element.galleryCardGap) || '0.5rem';
|
||||
const gap = normalizeRemValue(element.galleryCardGap) || '0.5rem';
|
||||
|
||||
return {
|
||||
display: 'grid',
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user