Appearance
Fix: __self__ animation targeting on choices + delay support
Issue: #1888Repo: Creative-Engine only (bug fix + delay). Delay UI in AF is a follow-up. Branch: TBD (off main in CE)
Problem
__self__ as animation target on choices (and all flow components) silently fails. The animation never plays.
Root cause
Three __self__ resolution sites emit animation:flow with comp.name (e.g., answer_0), but handleFlowAnimation does a direct string comparison against this.block?.blockName. No block has blockName === 'answer_0', so the event is dropped.
| File | Context | Resolves to | Works? |
|---|---|---|---|
Choice.vue:272 | Hover trigger | comp.name (answer_0) | No |
Choice.vue:292 | Click trigger | comp.name (answer_0) | No |
conversationFlow.ts:266 | Appear trigger | comp.name (statement_0) | No |
funcBlocks.ts:68 | Change operators | targetAbbrevOpName (t1) | Yes |
Change operators work because they resolve to targetAbbrevOpName (a proper abbreviation) and use animation:change + matchesBlockTarget().
Why it was never caught
- Targeting OTHER blocks from choices (e.g., choice ->
g1) works fine __self__on Change operators works (different resolution path)- The built-in choice appearing stagger (
animationDelay: ms(index * 120)) works but is hardcoded CSS, not flow-triggered handleFlowAnimationhas used direct comparison since its introduction;matchesBlockTarget()was added later but only applied tohandleChangeAnimationandhandleShowHideAnimation
Plan
Part 1: Fix __self__ resolution (3 files)
Choice.vue (2 sites: onHoverTrigger, doSelection)
Change:
ts
const resolvedTarget = targetBlock === '__self__' ? comp.name : targetBlockTo:
ts
const resolvedTarget = targetBlock === '__self__'
? abbrevName(comp.type, idFromNamePlusOne(comp.name), false)
: targetBlockImport abbrevName and idFromNamePlusOne from StyleAndClassNameGenerationMixin (already used in MessageHolder.vue).
conversationFlow.ts (1 site: emitAnimationTriggers)
Same change. Import the same helpers.
Part 2: Fix handleFlowAnimation matching (1 file)
AnimationMixin.ts
Change handleFlowAnimation from:
ts
handleFlowAnimation({ targetBlock, effects, duration, easing }) {
if (this.block?.blockName !== targetBlock) return
this.playFlowAnimation({ effects, duration, easing })
}To using matchesBlockTarget() + sub-element targeting (same pattern as handleChangeAnimation):
ts
handleFlowAnimation({ targetBlock, effects, duration, easing }) {
if (!this.matchesBlockTarget(targetBlock)) return
// Conversation sub-elements: animate the specific element, not the whole block
const isSubElement = this.block?.blockName === 'conversationProperties'
&& /^[mclir]\d+$/.test(targetBlock)
const subEl = isSubElement
? this.$el?.querySelector(`[data-showhide-target="${targetBlock}"]`)
: null
if (subEl) {
// Use Web Animations API directly on the sub-element
playDOMAnimation(subEl, effects, duration, easing)
return
}
this.playFlowAnimation({ effects, duration, easing })
}Note: playDOMAnimation is already defined in ShowHide.ts. Extract it to a shared util or import it. Alternatively, inline the Web Animations API call (it's ~10 lines).
Part 3: Add delay field to animation triggers
Data model change (all 3 repos):
Add optional delay?: number (milliseconds) to each target in animationTriggers.targets.
AF types (Blocks/data/types.ts):
ts
// In AnimationTriggerTarget or equivalent
delay?: number // ms before animation startsComposer (remapData.ts): Pass through -- delay is already on the target object, no special mapping needed.
Engine (Choice.vue, conversationFlow.ts):
ts
// Wrap emission in setTimeout when delay is set
const emit = () => DataStore.emitter.emit('animation:flow', { ... })
if (delay) setTimeout(emit, delay)
else emit()Frontend UI (follow-up, not in this branch): Add a number input for delay in OperatorAnimations.vue per target row.
Testing checklist
- [ ] Choice with
__self__click trigger -> choice element animates - [ ] Choice with
__self__hover trigger -> choice element animates on hover - [ ] Appear trigger with
__self__on a statement -> message element animates on appear - [ ] Choice targeting an explicit block (e.g.,
g1) still works (regression check) - [ ] Change operator
__self__still works (regression check) - [ ] ShowHide animations still work (regression check)
- [ ] Delay field: choice with 200ms delay animates after 200ms
- [ ] Multiple choices with staggered delays (0, 100, 200) animate sequentially
Future: Multi-select stagger UX
See issue comment. Multi-select operators in flow, apply one animation with auto-stagger delay. Acts as baseline -- individual operators can override. Purely frontend batch operation, no engine changes beyond the delay field.