import type { FormStyleSettings } from '@/types/form-style' import { formatDimensionToCss, resolveBorderStyle } from '@/utils/styleUtils' import { applyAlpha } from '@/utils/colorUtils' import { parseCssClassList } from '@/utils/form-styles/classParser' import { buildFormPopperScopeClass, buildFormStyleSelectors } from '@/utils/form-styles/selectors' import { resolveActiveColors, resolveCornerRadiusValue } from '@/utils/form-styles/styleSettings' import { applyFormContainerStyles } from '@/utils/form-styles/sections/formContainer' import { applyFormHeaderStyles } from '@/utils/form-styles/sections/formHeader' import { applyFieldLabelStyles } from '@/utils/form-styles/sections/fieldLabels' import { applyFieldStateVariantStyles } from '@/utils/form-styles/sections/fieldStateVariants' export { parseCssClassList } export function generateFormCSS( formId: string | number, styleSettings: FormStyleSettings | null, ): string { if (!styleSettings || styleSettings.stylesEnabled === false) { return '' } const { formSelector, baseSelector } = buildFormStyleSelectors( formId, styleSettings.customCssClass, ) const formPopperScopeClass = buildFormPopperScopeClass(formId) const css: string[] = [] const activeColors = resolveActiveColors(styleSettings) const formBackground = styleSettings.advancedMode?.general?.formBackground || styleSettings.quickMode?.formBackground || 'transparent' const formBorderColor = styleSettings.advancedMode?.general?.formBorderColor || styleSettings.quickMode?.formBorderColor const formBorderWidth = styleSettings.advancedMode?.general?.formBorderWidth || styleSettings.quickMode?.formBorderWidth const formBorderStyle = resolveBorderStyle(styleSettings.advancedMode?.general?.formBorderStyle) const formPaddingLeftRight = styleSettings.advancedMode?.general?.formPaddingLeftRight || '24px' const formPaddingTopBottom = styleSettings.advancedMode?.general?.formPaddingTopBottom || '24px' const verticalSpacing = styleSettings.advancedMode?.general?.verticalSpacing || styleSettings.quickMode?.verticalSpacing || '16px' const horizontalSpacing = styleSettings.advancedMode?.general?.horizontalSpacing || styleSettings.quickMode?.horizontalSpacing || '12px' const formAlignment = styleSettings.advancedMode?.general?.alignment || 'left' const formCornerRadius = resolveCornerRadiusValue( styleSettings.advancedMode?.general?.formCorner || styleSettings.quickMode?.formCorner, '8px', ) const fieldBackground = styleSettings.advancedMode?.fieldStates?.default?.background || styleSettings.quickMode?.fieldBackground || 'transparent' const fieldBorderColor = styleSettings.advancedMode?.fieldStates?.default?.borderColor || styleSettings.quickMode?.fieldBorderColor || '#6B737D' const fieldTextColor = styleSettings.advancedMode?.fieldStates?.default?.textColor || activeColors.fieldText const fieldFontWeight = styleSettings.advancedMode?.fieldStates?.default?.fontWeight const fieldFontStyle = styleSettings.advancedMode?.fieldStates?.default?.fontStyle const fieldFontSize = formatDimensionToCss( styleSettings.advancedMode?.fieldStates?.default?.fontSize, ) const fieldCornerRadius = resolveCornerRadiusValue( styleSettings.advancedMode?.fieldStates?.default?.corner || styleSettings.quickMode?.fieldCorner, '6px', ) const fieldPaddingX = styleSettings.advancedMode?.fieldStates?.default?.paddingLeftRight || styleSettings.quickMode?.fieldPaddingLeftRight || '8px' const fieldPaddingY = styleSettings.advancedMode?.fieldStates?.default?.paddingTopBottom || styleSettings.quickMode?.fieldPaddingTopBottom || '0px' const fieldBorderWidth = styleSettings.advancedMode?.fieldStates?.default?.borderWidth || styleSettings.quickMode?.fieldBorderWidth || '1px' const fieldBorderStyle = resolveBorderStyle( styleSettings.advancedMode?.fieldStates?.default?.borderStyle, ) const fieldDescriptionColor = styleSettings.advancedMode?.fieldStates?.default?.descriptionColor || '#212832' const buttonCornerRadius = resolveCornerRadiusValue( styleSettings.advancedMode?.buttonStates?.normal?.corner || styleSettings.corners?.buttonCornerRadius || styleSettings.button?.borderRadius, ) const buttonBackground = styleSettings.advancedMode?.buttonStates?.normal?.background || styleSettings.quickMode?.buttonBackground || '#FFFFFF' const buttonBorderColor = styleSettings.advancedMode?.buttonStates?.normal?.borderColor || styleSettings.quickMode?.buttonBorderColor || styleSettings.quickMode?.buttonBackground || '#707883' const buttonBorderWidth = styleSettings.advancedMode?.buttonStates?.normal?.borderWidth || styleSettings.quickMode?.buttonBorderWidth || '1px' const buttonTextColor = styleSettings.advancedMode?.buttonStates?.normal?.textColor || activeColors.buttonText applyFormContainerStyles(css, { formId, baseSelector, formSelector, formBackground, formBorderColor, formBorderWidth, formBorderStyle, formCornerRadius, formPaddingTopBottom, formPaddingLeftRight, formAlignment, verticalSpacing, horizontalSpacing, }) applyFormHeaderStyles(css, formId, styleSettings, activeColors) applyFieldLabelStyles(css, baseSelector, styleSettings, activeColors) // Text inputs and textareas (Element Plus wrappers) + phone/select/date/time variants - high specificity to override component styles const textAndPhoneWrappers = [ `${baseSelector} .ivyforms-input.el-input .el-input__wrapper`, `${baseSelector} .ivyforms-field .el-input__wrapper`, `${baseSelector} .ivyforms-phone-input .el-input__wrapper`, `${baseSelector} .ivyforms-field__phone .el-input__wrapper`, `${baseSelector} .ivyforms-field .vue-tel-input`, `${baseSelector} .ivyforms-field__select .el-input__wrapper`, `${baseSelector} .ivyforms-field__select .el-select__wrapper`, `${baseSelector} .el-select .el-select__wrapper`, `${baseSelector} .ivyforms-field__address .el-input__wrapper`, `${baseSelector} .ivyforms-field__date .el-input__wrapper`, `${baseSelector} .ivyforms-field__time .el-input__wrapper`, ] css.push(textAndPhoneWrappers.join(',') + ' {') if (fieldBackground) { css.push(` background-color: ${fieldBackground} !important;`) } if (fieldBorderColor) { css.push(` border: ${fieldBorderWidth} ${fieldBorderStyle} ${fieldBorderColor} !important;`) css.push(` box-shadow: none !important;`) } if (fieldCornerRadius) { css.push(` border-radius: ${fieldCornerRadius} !important;`) } css.push(` padding: ${fieldPaddingY} ${fieldPaddingX} !important;`) css.push(' box-sizing: border-box !important;') css.push('}') if (fieldBackground || fieldBorderColor || fieldCornerRadius) { css.push(`${baseSelector} .ivyforms-field__textarea .el-textarea__inner {`) if (fieldBackground) { css.push(` background-color: ${fieldBackground} !important;`) } if (fieldBorderColor) { css.push(` border: ${fieldBorderWidth} ${fieldBorderStyle} ${fieldBorderColor} !important;`) css.push(` box-shadow: none !important;`) } if (fieldCornerRadius) { css.push(` border-radius: ${fieldCornerRadius} !important;`) } css.push(` padding: ${fieldPaddingY} ${fieldPaddingX} !important;`) css.push(' box-sizing: border-box !important;') css.push('}') } // Ensure custom number-field wrappers match the same border-radius and clip inner backgrounds css.push( `${baseSelector} .ivyforms-text-number, ${baseSelector} .ivyforms-text-number__input, ${baseSelector} .el-input-number {`, ) if (fieldCornerRadius) { css.push(` border-radius: ${fieldCornerRadius} !important;`) } css.push(' overflow: hidden !important;') css.push('}') // Override fixed height on .ivyforms-input.el-input to allow padding to control size css.push(`${baseSelector} .ivyforms-input.el-input,`) css.push(`${baseSelector} .ivyforms-phone-input,`) css.push(`${baseSelector} .ivyforms-field__phone,`) css.push(`${baseSelector} .ivyforms-field__select,`) css.push(`${baseSelector} .ivyforms-field__address,`) css.push(`${baseSelector} .ivyforms-field__date,`) css.push(`${baseSelector} .ivyforms-field__time,`) css.push(`${baseSelector} .ivyforms-form-item-select .el-select__wrapper,`) css.push(`${baseSelector} .ivyforms-phone-input .vue-tel-input {`) css.push(' height: auto !important;') css.push(' min-height: 40px !important;') css.push('}') // Also override height on nested select/date picker elements css.push(`${baseSelector} .el-select,`) css.push(`${baseSelector} .el-date-picker,`) css.push(`${baseSelector} .el-time-picker {`) css.push(' height: auto !important;') css.push('}') // Override el-input height constraints css.push(`${baseSelector} .el-input {`) css.push(' height: auto !important;') css.push('}') // Ensure select and date picker inputs have consistent sizing css.push(`${baseSelector} .el-select .el-input,`) css.push(`${baseSelector} .ivyforms-field__select .el-input,`) css.push(`${baseSelector} .ivyforms-field__date .el-input,`) css.push(`${baseSelector} .ivyforms-field__time .el-input {`) css.push(' height: auto !important;') css.push('}') // Override date picker specific height constraint css.push(`${baseSelector} .ivyforms-date-picker .ivyforms-input .el-input__wrapper,`) css.push(`${baseSelector} .ivyforms-date-picker .ivyforms-input.el-input__wrapper {`) css.push(' height: auto !important;') css.push(' min-height: 40px !important;') css.push(` padding: ${fieldPaddingY} ${fieldPaddingX} !important;`) css.push('}') // Phone inner input (vue-tel-input) - remove inner borders to avoid double outline css.push(`${baseSelector} .vti__input {`) if (fieldTextColor) { css.push(` color: ${fieldTextColor} !important;`) } css.push(' border: none !important;') css.push(' box-shadow: none !important;') css.push(' background: transparent !important;') css.push(' padding: 0 !important;') css.push('}') // Ensure dropdowns display content vertically centered with flex css.push(`${baseSelector} .el-select__wrapper,`) css.push(`${baseSelector} .ivyforms-form-item-select .el-select__wrapper {`) css.push(' display: flex !important;') css.push(' align-items: center !important;') css.push('}') // Set select selection min-height to match text input inner height (30px) css.push(`${baseSelector} .el-select__selection {`) css.push(' min-height: 30px !important;') css.push('}') const selectWrappers = [ `${baseSelector} .el-select .el-input__wrapper`, `${baseSelector} .el-select__wrapper`, `${baseSelector} .ivyforms-field__select-input .el-select__wrapper`, ] css.push(selectWrappers.join(',') + ' {') if (fieldBackground) { css.push(` background-color: ${fieldBackground} !important;`) } if (fieldBorderColor) { css.push(` border: ${fieldBorderWidth} ${fieldBorderStyle} ${fieldBorderColor} !important;`) css.push(` box-shadow: none !important;`) } if (fieldCornerRadius) { css.push(` border-radius: ${fieldCornerRadius} !important;`) } css.push('}') // Select inner input - make transparent to avoid double borders css.push(`${baseSelector} .el-select .el-input__inner,`) css.push(`${baseSelector} .ivyforms-field__select .el-input__inner,`) css.push(`${baseSelector} .el-select__selected-item {`) if (fieldTextColor) { css.push(` color: ${fieldTextColor} !important;`) } css.push(' border: none !important;') css.push(' background: transparent !important;') css.push('}') // Reset select inner input padding to avoid covering borders css.push(`${baseSelector} .el-select__input {`) css.push(' padding: 0 !important;') css.push(' background: transparent !important;') css.push(' border: none !important;') css.push('}') // Country field in address - top/bottom borders only to match address row styling if (fieldBorderColor) { // Target using field class applied to country-address wrapper css.push(`${baseSelector} .country-address .el-select__wrapper {`) css.push( ` border-top: ${fieldBorderWidth} ${fieldBorderStyle} ${fieldBorderColor} !important;`, ) css.push( ` border-bottom: ${fieldBorderWidth} ${fieldBorderStyle} ${fieldBorderColor} !important;`, ) css.push(` border-left: none !important;`) css.push(` border-right: none !important;`) css.push('}') } if (fieldDescriptionColor) { css.push(`${baseSelector} .ivyforms-form-item__info-text {`) css.push(` color: ${fieldDescriptionColor} !important;`) css.push(` --ivyforms-info-color: ${fieldDescriptionColor} !important;`) css.push('}') } // Input text color if (fieldTextColor || fieldFontWeight || fieldFontStyle || fieldFontSize) { css.push(`${baseSelector} .el-input__inner,`) css.push(`${baseSelector} .el-textarea__inner,`) css.push(`${baseSelector} .ivyforms-field__phone .vti__input,`) css.push(`${baseSelector} .ivyforms-field__phone .vue-tel-input input,`) css.push(`${baseSelector} .ivyforms-field__select .el-select__selected-item,`) css.push(`${baseSelector} .el-select .el-select__selected-item {`) if (fieldTextColor) { css.push(` color: ${fieldTextColor} !important;`) } if (fieldFontWeight) { css.push(` font-weight: ${fieldFontWeight} !important;`) } if (fieldFontStyle) { css.push(` font-style: ${fieldFontStyle} !important;`) } if (fieldFontSize) { css.push(` font-size: ${fieldFontSize} !important;`) } css.push('}') } // Phone input (intl-tel-input) fallback css.push(`${baseSelector} .iti input,`) css.push(`${baseSelector} .iti__selected-flag,`) css.push(`${baseSelector} .iti__country-list,`) css.push(`${baseSelector} .vue-tel-input,`) css.push(`${baseSelector} .vti__input {`) if (fieldBackground) { css.push(` background-color: ${fieldBackground} !important;`) } if (fieldBorderColor) { css.push(` border-color: ${fieldBorderColor} !important;`) } if (fieldCornerRadius) { css.push(` border-radius: ${fieldCornerRadius} !important;`) } css.push('}') // Phone field text color (needs to be separate to avoid being overridden) if (fieldTextColor || fieldFontWeight || fieldFontStyle || fieldFontSize) { css.push(`${baseSelector} .vti__input {`) if (fieldTextColor) { css.push(` color: ${fieldTextColor} !important;`) } if (fieldFontWeight) { css.push(` font-weight: ${fieldFontWeight} !important;`) } if (fieldFontStyle) { css.push(` font-style: ${fieldFontStyle} !important;`) } if (fieldFontSize) { css.push(` font-size: ${fieldFontSize} !important;`) } css.push('}') } css.push(`${baseSelector} .choices__inner {`) if (fieldBackground) { css.push(` background-color: ${fieldBackground};`) } if (fieldBorderColor) { css.push(` border: ${fieldBorderWidth} ${fieldBorderStyle} ${fieldBorderColor};`) } if (fieldCornerRadius) { css.push(` border-radius: ${fieldCornerRadius};`) } css.push(` padding: ${fieldPaddingY} ${fieldPaddingX};`) css.push(' min-height: auto;') css.push('}') // Field placeholder const fieldPlaceholderPaddingLR = formatDimensionToCss( styleSettings.advancedMode?.fieldPlaceholder?.paddingLeftRight, ) const fieldPlaceholderAlignment = styleSettings.advancedMode?.fieldPlaceholder?.alignment || 'left' const fieldPlaceholderFontWeight = styleSettings.advancedMode?.fieldPlaceholder?.fontWeight const fieldPlaceholderFontStyle = styleSettings.advancedMode?.fieldPlaceholder?.fontStyle const fieldPlaceholderFontSize = formatDimensionToCss( styleSettings.advancedMode?.fieldPlaceholder?.fontSize, ) if ( activeColors.fieldPlaceholder || fieldPlaceholderAlignment !== 'left' || fieldPlaceholderFontWeight || fieldPlaceholderFontStyle || fieldPlaceholderFontSize ) { const placeholderColor = activeColors.fieldPlaceholder css.push(`${baseSelector} .el-input__inner::placeholder,`) css.push(`${baseSelector} .el-textarea__inner::placeholder,`) css.push(`${baseSelector} .el-select__placeholder,`) css.push(`${baseSelector} input::placeholder,`) css.push(`${baseSelector} textarea::placeholder {`) if (placeholderColor) { css.push(` color: ${placeholderColor} !important;`) } if (fieldPlaceholderPaddingLR) { css.push(` padding-left: ${fieldPlaceholderPaddingLR} !important;`) css.push(` padding-right: ${fieldPlaceholderPaddingLR} !important;`) } if (fieldPlaceholderAlignment !== 'left') { css.push(` text-align: ${fieldPlaceholderAlignment} !important;`) } if (fieldPlaceholderFontWeight) { css.push(` font-weight: ${fieldPlaceholderFontWeight} !important;`) } if (fieldPlaceholderFontStyle) { css.push(` font-style: ${fieldPlaceholderFontStyle} !important;`) } if (fieldPlaceholderFontSize) { css.push(` font-size: ${fieldPlaceholderFontSize} !important;`) } css.push('}') if (placeholderColor) { css.push(`${baseSelector} .el-select__placeholder {`) css.push(` color: ${placeholderColor} !important;`) css.push('}') } } if (fieldTextColor) { css.push(`${baseSelector} .ivyforms-field__textarea .el-textarea,`) css.push(`${baseSelector} .ivyforms-field__textarea .ivyforms-textarea {`) css.push(` --el-input-text-color: ${fieldTextColor};`) css.push('}') css.push(`${baseSelector} .ivyforms-field__textarea .el-textarea__inner,`) css.push(`${baseSelector} .ivyforms-field__phone .vti__input,`) css.push(`${baseSelector} .ivyforms-field__phone .vue-tel-input input {`) css.push(` color: ${fieldTextColor} !important;`) css.push('}') } applyFieldStateVariantStyles(css, { baseSelector, styleSettings, fieldBorderWidth, fieldTextColor, fieldFontSize, fieldFontWeight, fieldFontStyle, }) // Checkbox and Radio Selection (Element Plus components) const selectionColor = styleSettings.advancedMode?.checkboxRadio?.selectionColor || activeColors.checkboxRadioSelection || activeColors.checkboxRadioBorder // Border color for unchecked state - use activeColors which includes both quick and advanced const checkboxRadioBorderColor = activeColors.checkboxRadioBorder || selectionColor // Label color for unchecked state const checkboxRadioLabelColor = activeColors.checkboxRadioLabel const checkboxRadioFontWeight = styleSettings.advancedMode?.checkboxRadio?.fontWeight const checkboxRadioFontStyle = styleSettings.advancedMode?.checkboxRadio?.fontStyle const checkboxRadioFontSize = formatDimensionToCss( styleSettings.advancedMode?.checkboxRadio?.fontSize, ) if (selectionColor) { // Base border and radius for unchecked css.push(`${baseSelector} .ivyforms-checkbox .el-checkbox__input .el-checkbox__inner,`) css.push(`${baseSelector} .el-checkbox__input .el-checkbox__inner {`) css.push(` border-color: ${checkboxRadioBorderColor} !important;`) css.push(' border-width: 2px !important;') css.push(' border-radius: 4px !important;') css.push(' background-color: transparent !important;') css.push(' box-shadow: none !important;') css.push('}') css.push(`${baseSelector} .ivyforms-radio .el-radio__input .el-radio__inner,`) css.push(`${baseSelector} .el-radio__input .el-radio__inner {`) css.push(` border-color: ${checkboxRadioBorderColor} !important;`) css.push(' border-width: 2px !important;') css.push(' border-radius: 50% !important;') css.push(' background-color: transparent !important;') css.push(' box-shadow: none !important;') css.push('}') // Checkbox checked state - use selectionColor for FILL css.push( `${baseSelector} .ivyforms-checkbox.priority-primary .el-checkbox__input.is-checked .el-checkbox__inner,`, ) css.push( `${baseSelector} .ivyforms-checkbox .el-checkbox__input.is-checked .el-checkbox__inner,`, ) css.push(`${baseSelector} .el-checkbox__input.is-checked .el-checkbox__inner {`) css.push(` background-color: ${selectionColor} !important;`) css.push(` border-color: ${selectionColor} !important;`) css.push(' box-shadow: none !important;') css.push('}') // Checkbox checked inner marker - override priority colors css.push( `${baseSelector} .ivyforms-checkbox.priority-primary .el-checkbox__input.is-checked .el-checkbox__inner::after,`, ) css.push( `${baseSelector} .ivyforms-checkbox .el-checkbox__input.is-checked .el-checkbox__inner::after {`, ) css.push(` background-color: ${selectionColor} !important;`) css.push(` border-color: ${selectionColor} !important;`) css.push('}') // Radio checked state - use selectionColor for outer ring and inner dot css.push(`${baseSelector} .ivyforms-radio .el-radio__input.is-checked .el-radio__inner,`) css.push(`${baseSelector} .el-radio__input.is-checked .el-radio__inner {`) css.push(` border-color: ${selectionColor} !important;`) css.push(' background-color: transparent !important;') css.push(' box-shadow: none !important;') css.push('}') css.push(`${baseSelector} .ivyforms-radio .el-radio__input.is-checked .el-radio__inner::after,`) css.push(`${baseSelector} .el-radio__input.is-checked .el-radio__inner::after {`) css.push(` background-color: ${selectionColor} !important;`) css.push(' width: 8px !important;') css.push(' height: 8px !important;') css.push('}') } // Apply checkbox and radio label color if ( checkboxRadioLabelColor || checkboxRadioFontWeight || checkboxRadioFontStyle || checkboxRadioFontSize ) { css.push(`${baseSelector} .ivyforms-checkbox .el-checkbox__label,`) css.push(`${baseSelector} .el-checkbox__label {`) if (checkboxRadioLabelColor) { css.push(` color: ${checkboxRadioLabelColor} !important;`) } if (checkboxRadioFontWeight) { css.push(` font-weight: ${checkboxRadioFontWeight} !important;`) } if (checkboxRadioFontStyle) { css.push(` font-style: ${checkboxRadioFontStyle} !important;`) } if (checkboxRadioFontSize) { css.push(` font-size: ${checkboxRadioFontSize} !important;`) } css.push('}') css.push(`${baseSelector} .ivyforms-radio .el-radio__label,`) css.push(`${baseSelector} .el-radio__label {`) if (checkboxRadioLabelColor) { css.push(` color: ${checkboxRadioLabelColor} !important;`) } if (checkboxRadioFontWeight) { css.push(` font-weight: ${checkboxRadioFontWeight} !important;`) } if (checkboxRadioFontStyle) { css.push(` font-style: ${checkboxRadioFontStyle} !important;`) } if (checkboxRadioFontSize) { css.push(` font-size: ${checkboxRadioFontSize} !important;`) } css.push('}') } // Submit button (IvyButtonAction component) const buttonPaddingLR = formatDimensionToCss( styleSettings.advancedMode?.buttonStates?.normal?.paddingLeftRight, ) const buttonPaddingTB = formatDimensionToCss( styleSettings.advancedMode?.buttonStates?.normal?.paddingTopBottom, ) const hasButtonPaddingLR = buttonPaddingLR !== undefined && buttonPaddingLR !== '' const hasButtonPaddingTB = buttonPaddingTB !== undefined && buttonPaddingTB !== '' const buttonPaddingValue = (hasButtonPaddingTB ? buttonPaddingTB : '8px') + ' ' + (hasButtonPaddingLR ? buttonPaddingLR : '16px') const buttonWidthValue = formatDimensionToCss( styleSettings.advancedMode?.buttonStates?.normal?.width, ) const buttonHeightValue = formatDimensionToCss( styleSettings.advancedMode?.buttonStates?.normal?.height, ) const buttonFontWeight = styleSettings.advancedMode?.buttonStates?.normal?.fontWeight const buttonFontStyle = styleSettings.advancedMode?.buttonStates?.normal?.fontStyle const buttonFontSize = formatDimensionToCss( styleSettings.advancedMode?.buttonStates?.normal?.fontSize, ) css.push(`${baseSelector} .ivyforms-form-submit .ivyforms-button-action,`) css.push(`${baseSelector} .ivyforms-form-submit button.ivyforms-button-action {`) if (buttonBackground) { css.push(` background-color: ${buttonBackground} !important;`) } if (buttonTextColor) { css.push(` color: ${buttonTextColor} !important;`) } if (buttonFontWeight) { css.push(` font-weight: ${buttonFontWeight} !important;`) } if (buttonFontStyle) { css.push(` font-style: ${buttonFontStyle} !important;`) } if (buttonFontSize) { css.push(` font-size: ${buttonFontSize} !important;`) } css.push(` padding: ${buttonPaddingValue} !important;`) if (buttonWidthValue) { css.push(` width: ${buttonWidthValue} !important;`) } if (buttonHeightValue) { css.push(` height: ${buttonHeightValue} !important;`) } if (buttonBorderWidth && buttonBorderWidth !== '0px') { css.push( ` border: ${buttonBorderWidth} solid ${buttonBorderColor || buttonBackground} !important;`, ) } else { css.push(` border: none !important;`) } if (buttonCornerRadius) { css.push(` border-radius: ${buttonCornerRadius} !important;`) } css.push('}') // Button hover state - simple opacity effect (submit only) css.push(`${baseSelector} .ivyforms-form-submit .ivyforms-button-action:hover,`) css.push(`${baseSelector} .ivyforms-form-submit button.ivyforms-button-action:hover {`) css.push(' opacity: 0.9 !important;') css.push(' transform: translateY(-1px) !important;') css.push('}') // Success/Confirmation message const conf = styleSettings.advancedMode?.confirmationMessage || {} const successBgColor = conf.backgroundColor || '#F3F4F5' const confirmBorderColor = conf.borderColor || styleSettings.advancedMode?.general?.formBorderColor || styleSettings.quickMode?.formBorderColor || '#8B929C' const confirmBorderWidth = conf.borderWidth || styleSettings.advancedMode?.general?.formBorderWidth || styleSettings.quickMode?.formBorderWidth || '1px' const confirmBorderStyle = resolveBorderStyle( conf.borderStyle || styleSettings.advancedMode?.general?.formBorderStyle, ) const confirmCorner = resolveCornerRadiusValue( conf.corner || styleSettings.advancedMode?.general?.formCorner || styleSettings.quickMode?.formCorner, '12px', ) const successColor = conf.textColor css.push(`.ivyforms-form-wrapper-${formId} .ivyforms-success-message,`) css.push(`.ivyforms-form-wrapper-${formId} .ivyforms-confirmation-message {`) if (successBgColor) { css.push(` background-color: ${successBgColor} !important;`) } if (successColor) { css.push(` color: ${successColor} !important;`) } if (confirmBorderColor && confirmBorderWidth) { css.push( ` border: ${confirmBorderWidth} ${confirmBorderStyle} ${confirmBorderColor} !important;`, ) } else if (confirmBorderColor || confirmBorderWidth) { // Partial config if (confirmBorderColor) { css.push(` border-color: ${confirmBorderColor} !important;`) } if (confirmBorderWidth) { css.push(` border-width: ${confirmBorderWidth} !important;`) } css.push(` border-style: ${confirmBorderStyle} !important;`) } else { // No border configured css.push(' border: none !important;') } if (confirmCorner) { css.push(` border-radius: ${confirmCorner} !important;`) } css.push(' padding: 12px 16px !important;') css.push(' margin-bottom: 16px !important;') css.push('}') // Hide form wrapper border/background when confirmation is shown - but NOT for sample preview if (formId !== 'sample-preview') { css.push(`${baseSelector}:has(.ivyforms-success-message) {`) css.push(' background: transparent !important;') css.push(' border: none !important;') css.push(' padding: 0 !important;') css.push('}') } // Error message const errorMessageColor = styleSettings.advancedMode?.fieldStates?.error?.textColor if (errorMessageColor) { css.push(`${baseSelector} .el-form-item__error {`) css.push(` color: ${errorMessageColor} !important;`) css.push('}') } // Rating const ratingIconSize = styleSettings.advancedMode?.rating?.iconSize const ratingIconColor = styleSettings.advancedMode?.rating?.iconColor if (ratingIconSize) { css.push(`${baseSelector} .ivyforms-field__rating .ivyforms-rating__star .ivyforms-icon {`) css.push(` width: ${ratingIconSize} !important;`) css.push(` height: ${ratingIconSize} !important;`) css.push('}') css.push(`${baseSelector} .ivyforms-field__rating .ivyforms-rating__star .ivyforms-icon svg {`) css.push(` width: ${ratingIconSize} !important;`) css.push(` height: ${ratingIconSize} !important;`) css.push('}') } if (ratingIconColor) { css.push( `${baseSelector} .ivyforms-field__rating .ivyforms-rating__star.is-filled .ivyforms-icon,`, ) css.push( `${baseSelector} .ivyforms-field__rating .ivyforms-rating__star.is-hovered .ivyforms-icon {`, ) css.push(` color: ${ratingIconColor} !important;`) css.push('}') css.push( `${baseSelector} .ivyforms-field__rating .ivyforms-rating__star.is-filled .ivyforms-icon svg path,`, ) css.push( `${baseSelector} .ivyforms-field__rating .ivyforms-rating__star.is-hovered .ivyforms-icon svg path {`, ) css.push(` fill: ${ratingIconColor} !important;`) css.push(` stroke: ${ratingIconColor} !important;`) css.push('}') } // Date & Time Picker const dtCalendarBg = styleSettings.advancedMode?.dateTimePicker?.calendarBackground const dtNumberTextColor = styleSettings.advancedMode?.dateTimePicker?.numberTextColor const dtSelectionColor = styleSettings.advancedMode?.dateTimePicker?.selectionColor if (dtCalendarBg) { css.push(`.${formPopperScopeClass}.ivyforms-date-picker-popper.el-popper,`) css.push(`.${formPopperScopeClass}.ivyforms-date-picker-popper .el-picker-panel,`) css.push(`.${formPopperScopeClass}.ivyforms-time-popper.el-popper,`) css.push(`.${formPopperScopeClass}.ivyforms-time-popper .el-time-panel {`) css.push(` background-color: ${dtCalendarBg} !important;`) css.push('}') // Popper caret (Element Plus: .el-popper__arrow ::before draws the triangle) css.push( `.${formPopperScopeClass}.ivyforms-date-picker-popper.el-popper .el-popper__arrow::before,`, ) css.push( `.${formPopperScopeClass}.ivyforms-date-picker-popper.el-popper .el-popper__arrow:before,`, ) css.push(`.${formPopperScopeClass}.ivyforms-time-popper.el-popper .el-popper__arrow::before,`) css.push(`.${formPopperScopeClass}.ivyforms-time-popper.el-popper .el-popper__arrow:before {`) css.push(` background: ${dtCalendarBg} !important;`) css.push(` background-color: ${dtCalendarBg} !important;`) css.push(` border-color: ${dtCalendarBg} !important;`) css.push('}') } if (dtNumberTextColor) { css.push( `.${formPopperScopeClass}.ivyforms-date-picker-popper .el-date-table td .el-date-table-cell__text,`, ) css.push(`.${formPopperScopeClass}.ivyforms-date-picker-popper .el-date-picker__header-label,`) css.push(`.${formPopperScopeClass}.ivyforms-date-picker-popper .el-month-table td .cell,`) css.push(`.${formPopperScopeClass}.ivyforms-date-picker-popper .el-year-table td .cell,`) css.push(`.${formPopperScopeClass}.ivyforms-time-popper .el-time-spinner__item {`) css.push(` color: ${dtNumberTextColor} !important;`) css.push('}') } if (dtSelectionColor) { css.push( `.${formPopperScopeClass}.ivyforms-date-picker-popper .el-date-table td.current:not(.disabled) .el-date-table-cell__text,`, ) css.push( `.${formPopperScopeClass}.ivyforms-date-picker-popper .el-date-table td.available:hover .el-date-table-cell__text {`, ) css.push(` background-color: ${dtSelectionColor} !important;`) css.push(` border-color: ${dtSelectionColor} !important;`) css.push('}') css.push(`.${formPopperScopeClass}.ivyforms-time-popper .el-time-spinner__item.active,`) css.push(`.${formPopperScopeClass}.ivyforms-time-popper .el-time-spinner__item:hover {`) css.push(` color: ${dtSelectionColor} !important;`) css.push('}') css.push( `.${formPopperScopeClass}.ivyforms-date-picker-popper .el-date-table td.start-date .el-date-table-cell__text,`, ) css.push( `.${formPopperScopeClass}.ivyforms-date-picker-popper .el-date-table td.end-date .el-date-table-cell__text {`, ) css.push(` background-color: ${dtSelectionColor} !important;`) css.push('}') css.push( `.${formPopperScopeClass}.ivyforms-date-picker-popper .el-date-table td.in-range .el-date-table-cell {`, ) css.push(` background-color: ${applyAlpha(dtSelectionColor, '26')} !important;`) css.push('}') } // Select (Dropdown) const selectTagColor = styleSettings.advancedMode?.select?.tagColor const selectTagTextColor = styleSettings.advancedMode?.select?.tagTextColor const selectOptionHoverColor = styleSettings.advancedMode?.select?.optionHoverColor const selectSelectionTextColor = styleSettings.advancedMode?.select?.selectionTextColor const selectSelectionBackgroundColor = styleSettings.advancedMode?.select?.selectionBackgroundColor if (selectTagColor) { css.push(`${baseSelector} .ivyforms-field__select .ivyforms-form-item-select {`) css.push(` --ivyforms-select-tag-bg: ${selectTagColor} !important;`) css.push('}') css.push(`${baseSelector} .ivyforms-field__select .el-select .el-tag,`) css.push( `${baseSelector} .ivyforms-field__select .ivyforms-form-item-select .el-select__wrapper.is-focused .el-tag,`, ) css.push( `${baseSelector} .ivyforms-field__select .ivyforms-form-item-select .el-select__wrapper.is-active .el-tag,`, ) css.push( `${baseSelector} .ivyforms-field__select .ivyforms-form-item-select.is-secondary .el-select__wrapper.is-focused .el-tag,`, ) css.push( `${baseSelector} .ivyforms-field__select .ivyforms-form-item-select.is-secondary .el-select__wrapper.is-active .el-tag {`, ) css.push(` background: ${selectTagColor} !important;`) css.push(` background-color: ${selectTagColor} !important;`) css.push(` border-color: ${selectTagColor} !important;`) css.push('}') } if (selectTagTextColor) { css.push(`${baseSelector} .ivyforms-field__select .ivyforms-form-item-select {`) css.push(` --ivyforms-select-tag-text: ${selectTagTextColor} !important;`) css.push('}') css.push(`${baseSelector} .ivyforms-field__select .el-select .el-tag,`) css.push(`${baseSelector} .ivyforms-field__select .el-select .el-tag .el-tag__content,`) css.push(`${baseSelector} .ivyforms-field__select .el-select .el-select__tags-text,`) css.push(`${baseSelector} .ivyforms-field__select .el-select .el-select__selected-item,`) css.push(`${baseSelector} .ivyforms-field__select .el-select .el-tag .el-tag__close svg {`) css.push(` color: ${selectTagTextColor} !important;`) css.push('}') } if (selectOptionHoverColor) { css.push( `.${formPopperScopeClass}.ivyforms-select-popper .ivyforms-select-option.el-select-dropdown__item:hover,`, ) css.push( `.${formPopperScopeClass}.ivyforms-select-popper .ivyforms-select-option.el-select-dropdown__item.hover,`, ) css.push( `.${formPopperScopeClass}.ivyforms-select-popper .ivyforms-select-option.el-select-dropdown__item.is-hovering {`, ) css.push(` background-color: ${selectOptionHoverColor} !important;`) css.push('}') } if (selectSelectionTextColor) { css.push(`${baseSelector} .ivyforms-field__select .ivyforms-form-item-select {`) css.push(` --ivyforms-select-option-selected-text: ${selectSelectionTextColor} !important;`) css.push('}') css.push( `.${formPopperScopeClass}.ivyforms-select-popper .ivyforms-select-option.el-select-dropdown__item.is-selected,`, ) css.push( `.${formPopperScopeClass}.ivyforms-select-popper .ivyforms-select-option.el-select-dropdown__item.selected,`, ) css.push( `.${formPopperScopeClass}.ivyforms-select-popper .ivyforms-select-option.el-select-dropdown__item.is-selected.is-hovering,`, ) css.push( `.${formPopperScopeClass}.ivyforms-select-popper .ivyforms-select-option.el-select-dropdown__item.is-selected.is-secondary,`, ) css.push( `.${formPopperScopeClass}.ivyforms-select-popper.ivyforms-select-popper--secondary .ivyforms-select-option.el-select-dropdown__item.is-selected {`, ) css.push(` color: ${selectSelectionTextColor} !important;`) css.push('}') } if (selectSelectionBackgroundColor) { css.push(`${baseSelector} .ivyforms-field__select .ivyforms-form-item-select {`) css.push( ` --ivyforms-select-option-selected-bg: ${selectSelectionBackgroundColor} !important;`, ) css.push('}') css.push( `.${formPopperScopeClass}.ivyforms-select-popper .ivyforms-select-option.el-select-dropdown__item.is-selected,`, ) css.push( `.${formPopperScopeClass}.ivyforms-select-popper .ivyforms-select-option.el-select-dropdown__item.selected,`, ) css.push( `.${formPopperScopeClass}.ivyforms-select-popper .ivyforms-select-option.el-select-dropdown__item.is-selected.is-hovering,`, ) css.push( `.${formPopperScopeClass}.ivyforms-select-popper .ivyforms-select-option.el-select-dropdown__item.is-selected.is-secondary,`, ) css.push( `.${formPopperScopeClass}.ivyforms-select-popper.ivyforms-select-popper--secondary .ivyforms-select-option.el-select-dropdown__item.is-selected {`, ) css.push(` background-color: ${selectSelectionBackgroundColor} !important;`) css.push('}') } return css.join('\n') } /** * Inject CSS into the document * * @param formId - The form ID * @param css - The CSS string */ export function injectFormCSS(formId: string | number, css: string): void { const styleId = `ivyforms-style-${formId}` const existingStyle = document.getElementById(styleId) if (existingStyle) { existingStyle.remove() } const styleTag = document.createElement('style') styleTag.id = styleId styleTag.textContent = css document.head.appendChild(styleTag) } /** * Generate and inject form CSS * * @param formId - The form ID * @param styleSettings - The style settings */ export function applyFormStyles( formId: string | number, styleSettings: FormStyleSettings | null, ): void { const css = generateFormCSS(formId, styleSettings) injectFormCSS(formId, css) }