Skip to content

State Preview System

Overview

The state preview system allows users to preview different component states (hover, focus, disabled, loading, success, error, etc.) in the configuration builder without affecting runtime behavior. This is achieved through the isStatePreview flag that separates "preview mode" from "runtime mode".

Applies to:

  • Submit buttons (loading, success, error, disabled states)
  • Form inputs (hover, focus, disabled states)
  • Can be extended to other components

Data Flow at a Glance

User clicks state button in UI

StateSection.vue (handleStateClick)

Emits: update:state + update:isStatePreview

FormSubmitButtonConfiguration.vue (@update:isStatePreview)

updateValue('isStatePreview', true/false)

Vuex Store (blockData.isStatePreview)

Creative-Engine (FormSubmitButton.vue)

htmlDisabled & .disabled class logic

Button renders with preview colors (not disabled styling)

Key Files & Their Roles

FileRoleKey Logic
StateSection.vueUI for state selectionEmits update:isStatePreview when state clicked
FormSubmitButtonConfiguration.vueConfiguration handlerListens to @update:isStatePreview and calls updateValue()
defaults.tsDefault valuesisStatePreview: false
types.tsType definitionsisStatePreview?: boolean in FormSubmitButtonProperties
form.ts (CE)Runtime typesisStatePreview?: boolean in SubmitButtonBlock
FormSubmitButton.vue (CE)Button rendererUses isStatePreview to control .disabled class and htmlDisabled
CreativeFormBlock.vue (CE)CSS styling.disabled class styling that gets skipped when isStatePreview === true

How It Works

1. User Selects a State

vue
<!-- StateSection.vue -->
<OptionButton @click="handleStateClick(option)">
  {{ option }}
</OptionButton>

handleStateClick(state) {
  this.$emit('update:state', state)
  this.$emit('update:isStatePreview', true)  // ← ALL states are previews
}

2. Configuration Receives & Stores

vue
<!-- FormSubmitButtonConfiguration.vue -->
<StateSection
  @update:isStatePreview="updateValue($event, 'isStatePreview')"
/>

This calls the updateValue() method from configurationLogic mixin, which updates the Vuex store.

3. Engine Receives the Flag

The blockData (including isStatePreview) flows to Creative-Engine through props:

typescript
// FormSubmitButton.vue receives props.block with isStatePreview
const props = defineProps<{
  block: SubmitButtonBlock  // Contains isStatePreview
}>()

4. Button Rendering Logic

typescript
// Determine if .disabled class should be applied
disabled: isButtonDisabled && !props.block.isStatePreview && props.block.submitState === 'default'

// Determine if HTML disabled attribute should be set
const htmlDisabled = computed(() => {
  if (props.block.isStatePreview || props.block.submitState !== 'default') {
    return false  // ← Don't disable HTML element in preview
  }
  return isButtonDisabled.value
})

Result: When isStatePreview === true:

  • .disabled CSS class is NOT applied → colors show correctly
  • HTML disabled attribute is NOT set → button is interactive
  • User sees the preview colors even with required fields

5. Cleanup on Navigation

typescript
// configurationLogic.ts (mixin)
beforeDestroy() {
  if (this.blockData?.submitState) {
    this.updateValue('default', 'submitState')
    this.updateValue(false, 'isStatePreview')  // ← Reset flag
  }
}

When user navigates away from submit button config:

  • submitState resets to 'default'
  • isStatePreview resets to false
  • Button shows runtime disabled state (if required fields present)

Why This Approach?

Advantages of isStatePreview Flag

  1. Explicit Intent - Code clearly states "we are in preview mode"
  2. Scalable - Same pattern works for all component states (hover, active, focus, etc.)
  3. Separates Concerns - Flag handles "is this preview?" independently from "what state is shown?"
  4. Debuggable - Easy to see in DevTools: isStatePreview: true/false
  5. Future-Proof - Can be reused for previewing other components

Alternative (Not Used)

Could have used submitState !== 'default' to determine preview mode, but:

  • ❌ Mixes "what state" with "is this preview"
  • ❌ Doesn't scale to other states (hover, active, etc.)
  • ❌ Harder to maintain and understand

State Transitions

Runtime (isStatePreview = false)

User opens submit button config

User clicks state button

Preview Mode (isStatePreview = true)

User sees preview colors (disabled styling bypassed)

User navigates to different block

beforeDestroy() fires

Runtime (isStatePreview = false)

Button shows actual disabled state (if required fields present)

Form Input State System

How It Works

Form inputs use a dual system for state styling:

  1. Preview Mode (.state-hover, .state-focus, .state-disabled)

    • Applied when isStatePreview: true and user selects a state
    • Shows preview colors from configuration
    • Allows user to see and edit state styles
  2. Runtime Mode (:hover, :focus, :disabled)

    • Applied during actual form usage
    • Uses same values from globalInputStyling.hoverStyle, focusStyle, disabledStyle
    • Ensures preview matches runtime behavior

Implementation

Creative-Engine (CreativeFormBlock.vue)

typescript
getInputPseudoClassStyles() {
  const globalStyling = formProps.globalInputStyling || {}
  
  // Build styles from hoverStyle, focusStyle, disabledStyle
  return {
    hover: buildStateStyles(globalStyling.hoverStyle),
    focus: buildStateStyles(globalStyling.focusStyle),
    disabled: buildStateStyles(globalStyling.disabledStyle),
  }
}

// In styles() computed property:
[`.form-input-field:hover`]: pseudoClassStyles.hover || fallback,
[`.form-input-field:focus`]: pseudoClassStyles.focus || fallback,
[`.form-input-field:disabled`]: pseudoClassStyles.disabled || fallback,

Application-Frontend (FormConfiguration.vue)

State configuration is stored in:

json
{
  "globalInputStyling": {
    "hoverStyle": {
      "backgroundSettings": { "color": "#8AB0FF" },
      "borderStyle": { "borderColor": "#CBD5E0" },
      "boxShadowSettings": { "blur": "12px", "color": "#0000002E" }
    },
    "focusStyle": { ... },
    "disabledStyle": { ... }
  }
}

Key Difference from Submit Button

AspectSubmit ButtonForm Inputs
StatesCustom (loading, success, error)Standard pseudo-classes (hover, focus, disabled)
Preview Classes.state-loading, .state-success.state-hover, .state-focus
Runtime Styles:hover (generic), .disabled (custom):hover, :focus, :disabled (all from config)
PurposeShow submission statesShow interaction states

Why This Approach?

For Form Inputs:

  • ✅ Preview styles match runtime styles exactly
  • ✅ User sees what they get (WYSIWYG)
  • ✅ Single source of truth for state styling
  • ✅ Pseudo-classes use configuration values, not hardcoded

For Submit Button:

  • ✅ Custom states (loading, success, error) for submission flow
  • ✅ Standard hover/focus use generic browser behavior
  • ✅ Disabled state uses custom styling from config

Testing Checklist

Submit Button

  • [ ] Select "default" state → isStatePreview: true, colors visible
  • [ ] Select "loading" state → isStatePreview: true, animation visible
  • [ ] Select "success" state → isStatePreview: true, checkmark visible
  • [ ] Select "error" state → isStatePreview: true, error icon visible
  • [ ] Select "disabled" state → isStatePreview: true, disabled colors visible
  • [ ] Navigate to different block → isStatePreview: false, submitState: 'default'
  • [ ] Form with required fields → button shows disabled in runtime
  • [ ] Form with required fields + in config → button shows preview colors

Form Inputs

  • [ ] Select "hover" state → .state-hover class applied with config colors
  • [ ] Select "focus" state → .state-focus class applied with config colors
  • [ ] Select "disabled" state → .state-disabled class applied with config colors
  • [ ] Hover over input in runtime → :hover pseudo-class uses same config colors
  • [ ] Focus input in runtime → :focus pseudo-class uses same config colors
  • [ ] Disabled input in runtime → :disabled pseudo-class uses same config colors
  • [ ] Change hover color in config → both preview and runtime update
  • [ ] Navigate away from form → isStatePreview: false, inputState: 'default'

Internal documentation