@pythoughts/vue-skills-mcp 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +63 -0
- package/index.mjs +139 -0
- package/package.json +34 -0
- package/skills/create-adaptable-composable/SKILL.md +76 -0
- package/skills/vue-best-practices/SKILL.md +154 -0
- package/skills/vue-best-practices/references/animation-class-based-technique.md +254 -0
- package/skills/vue-best-practices/references/animation-state-driven-technique.md +291 -0
- package/skills/vue-best-practices/references/component-async.md +97 -0
- package/skills/vue-best-practices/references/component-data-flow.md +350 -0
- package/skills/vue-best-practices/references/component-fallthrough-attrs.md +174 -0
- package/skills/vue-best-practices/references/component-keep-alive.md +137 -0
- package/skills/vue-best-practices/references/component-slots.md +216 -0
- package/skills/vue-best-practices/references/component-suspense.md +228 -0
- package/skills/vue-best-practices/references/component-teleport.md +108 -0
- package/skills/vue-best-practices/references/component-transition-group.md +128 -0
- package/skills/vue-best-practices/references/component-transition.md +125 -0
- package/skills/vue-best-practices/references/composables.md +290 -0
- package/skills/vue-best-practices/references/directives.md +162 -0
- package/skills/vue-best-practices/references/perf-avoid-component-abstraction-in-lists.md +159 -0
- package/skills/vue-best-practices/references/perf-v-once-v-memo-directives.md +182 -0
- package/skills/vue-best-practices/references/perf-virtualize-large-lists.md +187 -0
- package/skills/vue-best-practices/references/plugins.md +166 -0
- package/skills/vue-best-practices/references/reactivity.md +346 -0
- package/skills/vue-best-practices/references/render-functions.md +201 -0
- package/skills/vue-best-practices/references/sfc.md +310 -0
- package/skills/vue-best-practices/references/state-management.md +135 -0
- package/skills/vue-best-practices/references/updated-hook-performance.md +187 -0
- package/skills/vue-debug-guides/SKILL.md +205 -0
- package/skills/vue-debug-guides/reference/animation-key-for-rerender.md +160 -0
- package/skills/vue-debug-guides/reference/animation-transitiongroup-performance.md +241 -0
- package/skills/vue-debug-guides/reference/async-component-error-handling.md +115 -0
- package/skills/vue-debug-guides/reference/async-component-keepalive-ref-issue.md +112 -0
- package/skills/vue-debug-guides/reference/async-component-suspense-control.md +84 -0
- package/skills/vue-debug-guides/reference/async-component-vue-router.md +109 -0
- package/skills/vue-debug-guides/reference/attrs-event-listener-merging.md +205 -0
- package/skills/vue-debug-guides/reference/checkbox-true-false-value-form-submission.md +118 -0
- package/skills/vue-debug-guides/reference/cleanup-side-effects.md +172 -0
- package/skills/vue-debug-guides/reference/click-events-on-components.md +180 -0
- package/skills/vue-debug-guides/reference/component-naming-conflicts.md +159 -0
- package/skills/vue-debug-guides/reference/component-ref-requires-defineexpose.md +176 -0
- package/skills/vue-debug-guides/reference/composable-avoid-hidden-side-effects.md +208 -0
- package/skills/vue-debug-guides/reference/composable-call-location-restrictions.md +141 -0
- package/skills/vue-debug-guides/reference/composable-naming-return-pattern.md +139 -0
- package/skills/vue-debug-guides/reference/composable-tovalue-inside-watcheffect.md +182 -0
- package/skills/vue-debug-guides/reference/composition-api-not-functional-programming.md +120 -0
- package/skills/vue-debug-guides/reference/composition-api-script-setup-async-context.md +203 -0
- package/skills/vue-debug-guides/reference/composition-api-vs-react-hooks-differences.md +156 -0
- package/skills/vue-debug-guides/reference/computed-array-mutation.md +148 -0
- package/skills/vue-debug-guides/reference/computed-conditional-dependencies.md +147 -0
- package/skills/vue-debug-guides/reference/computed-no-parameters.md +159 -0
- package/skills/vue-debug-guides/reference/computed-no-side-effects.md +107 -0
- package/skills/vue-debug-guides/reference/computed-return-value-readonly.md +160 -0
- package/skills/vue-debug-guides/reference/configure-app-before-mount.md +89 -0
- package/skills/vue-debug-guides/reference/declare-emits-for-documentation.md +212 -0
- package/skills/vue-debug-guides/reference/define-expose-before-await.md +192 -0
- package/skills/vue-debug-guides/reference/define-model-default-value-sync.md +139 -0
- package/skills/vue-debug-guides/reference/defineEmits-must-be-top-level.md +164 -0
- package/skills/vue-debug-guides/reference/defineEmits-no-runtime-and-type-mixed.md +170 -0
- package/skills/vue-debug-guides/reference/definemodel-object-mutation-no-emit.md +148 -0
- package/skills/vue-debug-guides/reference/dom-update-timing-nexttick.md +90 -0
- package/skills/vue-debug-guides/reference/dynamic-argument-constraints.md +146 -0
- package/skills/vue-debug-guides/reference/dynamic-component-registration-vite.md +147 -0
- package/skills/vue-debug-guides/reference/event-modifier-order-matters.md +101 -0
- package/skills/vue-debug-guides/reference/exact-modifier-for-precise-shortcuts.md +155 -0
- package/skills/vue-debug-guides/reference/fallthrough-attrs-overwrite-vue3.md +159 -0
- package/skills/vue-debug-guides/reference/in-dom-template-parsing-caveats.md +149 -0
- package/skills/vue-debug-guides/reference/inheritattrs-false-for-wrapper-components.md +230 -0
- package/skills/vue-debug-guides/reference/keepalive-router-nested-double-mount.md +222 -0
- package/skills/vue-debug-guides/reference/keepalive-transition-memory-leak.md +144 -0
- package/skills/vue-debug-guides/reference/keyup-modifier-timing.md +137 -0
- package/skills/vue-debug-guides/reference/lifecycle-dom-access-timing.md +216 -0
- package/skills/vue-debug-guides/reference/lifecycle-hooks-synchronous-registration.md +156 -0
- package/skills/vue-debug-guides/reference/lifecycle-ssr-awareness.md +184 -0
- package/skills/vue-debug-guides/reference/local-components-not-in-descendants.md +151 -0
- package/skills/vue-debug-guides/reference/mount-return-value.md +88 -0
- package/skills/vue-debug-guides/reference/multi-root-component-class-attrs.md +93 -0
- package/skills/vue-debug-guides/reference/native-event-collision-with-emits.md +162 -0
- package/skills/vue-debug-guides/reference/no-passive-with-prevent.md +141 -0
- package/skills/vue-debug-guides/reference/no-v-if-with-v-for.md +136 -0
- package/skills/vue-debug-guides/reference/perf-computed-object-stability.md +157 -0
- package/skills/vue-debug-guides/reference/perf-props-stability-update-optimization.md +140 -0
- package/skills/vue-debug-guides/reference/plugin-global-properties-sparingly.md +109 -0
- package/skills/vue-debug-guides/reference/plugin-install-before-mount.md +124 -0
- package/skills/vue-debug-guides/reference/plugin-prefer-provide-inject-over-global-properties.md +120 -0
- package/skills/vue-debug-guides/reference/plugin-typescript-type-augmentation.md +157 -0
- package/skills/vue-debug-guides/reference/prop-defineprops-scope-limitation.md +161 -0
- package/skills/vue-debug-guides/reference/provide-inject-debugging-challenges.md +203 -0
- package/skills/vue-debug-guides/reference/provide-inject-default-value-factory.md +244 -0
- package/skills/vue-debug-guides/reference/provide-inject-reactivity-not-automatic.md +226 -0
- package/skills/vue-debug-guides/reference/provide-inject-synchronous-setup.md +235 -0
- package/skills/vue-debug-guides/reference/reactive-destructuring.md +89 -0
- package/skills/vue-debug-guides/reference/reactivity-debugging-hooks.md +132 -0
- package/skills/vue-debug-guides/reference/reactivity-markraw-for-non-reactive.md +149 -0
- package/skills/vue-debug-guides/reference/reactivity-proxy-identity-hazard.md +96 -0
- package/skills/vue-debug-guides/reference/reactivity-same-tick-batching.md +166 -0
- package/skills/vue-debug-guides/reference/ref-value-access.md +61 -0
- package/skills/vue-debug-guides/reference/refs-in-collections-need-value.md +81 -0
- package/skills/vue-debug-guides/reference/render-function-avoid-internal-vnode-properties.md +151 -0
- package/skills/vue-debug-guides/reference/render-function-vnodes-must-be-unique.md +133 -0
- package/skills/vue-debug-guides/reference/rendering-render-function-h-import-vue3.md +148 -0
- package/skills/vue-debug-guides/reference/rendering-render-function-return-from-setup.md +148 -0
- package/skills/vue-debug-guides/reference/rendering-render-function-slots-as-functions.md +168 -0
- package/skills/vue-debug-guides/reference/rendering-resolve-component-for-string-names.md +231 -0
- package/skills/vue-debug-guides/reference/select-initial-value-ios-bug.md +91 -0
- package/skills/vue-debug-guides/reference/self-referencing-component-name.md +157 -0
- package/skills/vue-debug-guides/reference/sfc-named-exports-forbidden.md +184 -0
- package/skills/vue-debug-guides/reference/sfc-scoped-css-child-component-styling.md +156 -0
- package/skills/vue-debug-guides/reference/sfc-scoped-css-dynamic-content.md +193 -0
- package/skills/vue-debug-guides/reference/sfc-scoped-css-slot-content.md +242 -0
- package/skills/vue-debug-guides/reference/sfc-script-setup-reactivity.md +195 -0
- package/skills/vue-debug-guides/reference/slot-forwarding-to-child-components.md +143 -0
- package/skills/vue-debug-guides/reference/slot-implicit-default-content.md +155 -0
- package/skills/vue-debug-guides/reference/slot-name-reserved-prop.md +109 -0
- package/skills/vue-debug-guides/reference/slot-named-scoped-explicit-default.md +95 -0
- package/skills/vue-debug-guides/reference/slot-render-scope-parent-only.md +135 -0
- package/skills/vue-debug-guides/reference/slot-v-slot-on-components-or-templates-only.md +122 -0
- package/skills/vue-debug-guides/reference/ssr-hydration-mismatch-causes.md +280 -0
- package/skills/vue-debug-guides/reference/ssr-platform-specific-apis.md +256 -0
- package/skills/vue-debug-guides/reference/state-ssr-cross-request-pollution.md +276 -0
- package/skills/vue-debug-guides/reference/suspense-no-builtin-error-handling.md +127 -0
- package/skills/vue-debug-guides/reference/suspense-ssr-hydration-issues.md +159 -0
- package/skills/vue-debug-guides/reference/tailwind-dynamic-class-generation.md +144 -0
- package/skills/vue-debug-guides/reference/teleport-scoped-styles-limitation.md +191 -0
- package/skills/vue-debug-guides/reference/teleport-ssr-hydration.md +152 -0
- package/skills/vue-debug-guides/reference/teleport-target-must-exist.md +113 -0
- package/skills/vue-debug-guides/reference/template-expressions-restrictions.md +114 -0
- package/skills/vue-debug-guides/reference/template-functions-no-side-effects.md +187 -0
- package/skills/vue-debug-guides/reference/template-ref-null-with-v-if.md +123 -0
- package/skills/vue-debug-guides/reference/template-ref-unwrapping-top-level.md +104 -0
- package/skills/vue-debug-guides/reference/template-ref-v-for-order.md +172 -0
- package/skills/vue-debug-guides/reference/textarea-no-interpolation.md +72 -0
- package/skills/vue-debug-guides/reference/transition-group-flip-inline-elements.md +152 -0
- package/skills/vue-debug-guides/reference/transition-group-move-animation-position-absolute.md +130 -0
- package/skills/vue-debug-guides/reference/transition-group-no-default-wrapper-vue3.md +152 -0
- package/skills/vue-debug-guides/reference/transition-js-hooks-done-callback.md +251 -0
- package/skills/vue-debug-guides/reference/transition-nested-duration.md +182 -0
- package/skills/vue-debug-guides/reference/transition-reusable-scoped-style.md +245 -0
- package/skills/vue-debug-guides/reference/transition-router-view-appear.md +193 -0
- package/skills/vue-debug-guides/reference/transition-type-when-mixed.md +172 -0
- package/skills/vue-debug-guides/reference/transition-unmount-hook-timing.md +149 -0
- package/skills/vue-debug-guides/reference/ts-defineprops-boolean-default-false.md +225 -0
- package/skills/vue-debug-guides/reference/ts-defineprops-imported-types-limitations.md +281 -0
- package/skills/vue-debug-guides/reference/ts-event-handler-explicit-typing.md +213 -0
- package/skills/vue-debug-guides/reference/ts-reactive-no-generic-argument.md +196 -0
- package/skills/vue-debug-guides/reference/ts-shallowref-for-dynamic-components.md +218 -0
- package/skills/vue-debug-guides/reference/ts-template-ref-null-handling.md +249 -0
- package/skills/vue-debug-guides/reference/ts-template-type-casting.md +214 -0
- package/skills/vue-debug-guides/reference/ts-withdefaults-mutable-factory-function.md +171 -0
- package/skills/vue-debug-guides/reference/undeclared-emits-double-firing.md +195 -0
- package/skills/vue-debug-guides/reference/use-template-ref-vue35.md +158 -0
- package/skills/vue-debug-guides/reference/v-else-must-follow-v-if.md +136 -0
- package/skills/vue-debug-guides/reference/v-for-component-props.md +95 -0
- package/skills/vue-debug-guides/reference/v-for-computed-reverse-sort.md +86 -0
- package/skills/vue-debug-guides/reference/v-for-key-attribute.md +90 -0
- package/skills/vue-debug-guides/reference/v-for-range-starts-at-one.md +66 -0
- package/skills/vue-debug-guides/reference/v-if-null-check-order.md +171 -0
- package/skills/vue-debug-guides/reference/v-model-ignores-html-attributes.md +83 -0
- package/skills/vue-debug-guides/reference/v-model-ime-composition.md +83 -0
- package/skills/vue-debug-guides/reference/v-model-number-modifier-behavior.md +124 -0
- package/skills/vue-debug-guides/reference/v-show-template-limitation.md +124 -0
- package/skills/vue-debug-guides/reference/watch-async-cleanup.md +180 -0
- package/skills/vue-debug-guides/reference/watch-async-creation-memory-leak.md +176 -0
- package/skills/vue-debug-guides/reference/watch-deep-same-object-reference.md +165 -0
- package/skills/vue-debug-guides/reference/watch-flush-timing.md +189 -0
- package/skills/vue-debug-guides/reference/watch-reactive-property-getter.md +108 -0
- package/skills/vue-debug-guides/reference/watcheffect-async-dependency-tracking.md +173 -0
- package/skills/vue-debug-guides/reference/watcheffect-flush-post-for-refs.md +176 -0
- package/skills/vue-jsx-best-practices/SKILL.md +12 -0
- package/skills/vue-jsx-best-practices/reference/render-function-jsx-vue-vs-react.md +141 -0
- package/skills/vue-options-api-best-practices/SKILL.md +23 -0
- package/skills/vue-options-api-best-practices/reference/no-arrow-functions-in-lifecycle-hooks.md +95 -0
- package/skills/vue-options-api-best-practices/reference/no-arrow-functions-in-methods.md +68 -0
- package/skills/vue-options-api-best-practices/reference/stateful-methods-lifecycle.md +61 -0
- package/skills/vue-options-api-best-practices/reference/ts-options-api-arrow-functions-validators.md +141 -0
- package/skills/vue-options-api-best-practices/reference/ts-options-api-computed-return-types.md +192 -0
- package/skills/vue-options-api-best-practices/reference/ts-options-api-proptype-complex-types.md +212 -0
- package/skills/vue-options-api-best-practices/reference/ts-options-api-provide-inject-limitations.md +135 -0
- package/skills/vue-options-api-best-practices/reference/ts-options-api-type-event-handlers.md +202 -0
- package/skills/vue-options-api-best-practices/reference/ts-options-api-use-definecomponent.md +172 -0
- package/skills/vue-options-api-best-practices/reference/ts-strict-mode-options-api.md +197 -0
- package/skills/vue-pinia-best-practices/SKILL.md +21 -0
- package/skills/vue-pinia-best-practices/reference/pinia-no-active-pinia-error.md +248 -0
- package/skills/vue-pinia-best-practices/reference/pinia-setup-store-return-all-state.md +227 -0
- package/skills/vue-pinia-best-practices/reference/pinia-store-destructuring-breaks-reactivity.md +193 -0
- package/skills/vue-pinia-best-practices/reference/state-url-for-ephemeral-filters.md +238 -0
- package/skills/vue-pinia-best-practices/reference/state-use-pinia-for-large-apps.md +262 -0
- package/skills/vue-pinia-best-practices/reference/store-method-binding-parentheses.md +191 -0
- package/skills/vue-router-best-practices/SKILL.md +23 -0
- package/skills/vue-router-best-practices/reference/router-beforeenter-no-param-trigger.md +167 -0
- package/skills/vue-router-best-practices/reference/router-beforerouteenter-no-this.md +176 -0
- package/skills/vue-router-best-practices/reference/router-guard-async-await-pattern.md +227 -0
- package/skills/vue-router-best-practices/reference/router-navigation-guard-infinite-loop.md +187 -0
- package/skills/vue-router-best-practices/reference/router-navigation-guard-next-deprecated.md +150 -0
- package/skills/vue-router-best-practices/reference/router-param-change-no-lifecycle.md +181 -0
- package/skills/vue-router-best-practices/reference/router-simple-routing-cleanup.md +209 -0
- package/skills/vue-router-best-practices/reference/router-use-vue-router-for-production.md +183 -0
- package/skills/vue-testing-best-practices/SKILL.md +29 -0
- package/skills/vue-testing-best-practices/reference/async-component-testing.md +163 -0
- package/skills/vue-testing-best-practices/reference/teleport-testing-complexity.md +158 -0
- package/skills/vue-testing-best-practices/reference/testing-async-await-flushpromises.md +175 -0
- package/skills/vue-testing-best-practices/reference/testing-browser-vs-node-runners.md +208 -0
- package/skills/vue-testing-best-practices/reference/testing-component-blackbox-approach.md +144 -0
- package/skills/vue-testing-best-practices/reference/testing-composables-helper-wrapper.md +238 -0
- package/skills/vue-testing-best-practices/reference/testing-e2e-playwright-recommended.md +242 -0
- package/skills/vue-testing-best-practices/reference/testing-no-snapshot-only.md +197 -0
- package/skills/vue-testing-best-practices/reference/testing-pinia-store-setup.md +228 -0
- package/skills/vue-testing-best-practices/reference/testing-suspense-async-components.md +229 -0
- package/skills/vue-testing-best-practices/reference/testing-vitest-recommended-for-vue.md +204 -0
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Specify Explicit Duration for Nested Transitions
|
|
3
|
+
impact: MEDIUM
|
|
4
|
+
impactDescription: Nested transitions with different timings may end prematurely when Vue detects only the root element's transition end
|
|
5
|
+
type: gotcha
|
|
6
|
+
tags: [vue3, transition, animation, duration, nested, timing]
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Specify Explicit Duration for Nested Transitions
|
|
10
|
+
|
|
11
|
+
**Impact: MEDIUM** - When transitioning elements that contain nested child elements with different animation timings, Vue by default listens only for the first `transitionend` or `animationend` event on the **root** transition element. This means if inner elements have longer or delayed animations, they may be cut off when the root element's transition completes.
|
|
12
|
+
|
|
13
|
+
## Task Checklist
|
|
14
|
+
|
|
15
|
+
- [ ] Identify if your transition contains nested elements with different animation durations
|
|
16
|
+
- [ ] Use the `:duration` prop to specify the total time Vue should wait
|
|
17
|
+
- [ ] Consider using separate enter and leave durations if they differ
|
|
18
|
+
- [ ] Test animations to ensure nested elements complete fully
|
|
19
|
+
|
|
20
|
+
**Problematic Code:**
|
|
21
|
+
```vue
|
|
22
|
+
<template>
|
|
23
|
+
<!-- BAD: Inner element has longer animation that gets cut off -->
|
|
24
|
+
<Transition name="nested">
|
|
25
|
+
<div v-if="show" class="outer">
|
|
26
|
+
<div class="inner">Hello</div>
|
|
27
|
+
</div>
|
|
28
|
+
</Transition>
|
|
29
|
+
</template>
|
|
30
|
+
|
|
31
|
+
<style>
|
|
32
|
+
.nested-enter-active .outer,
|
|
33
|
+
.nested-leave-active .outer {
|
|
34
|
+
transition: opacity 0.3s ease;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.nested-enter-active .inner,
|
|
38
|
+
.nested-leave-active .inner {
|
|
39
|
+
/* This 0.5s animation gets cut off at 0.3s when outer finishes! */
|
|
40
|
+
transition: transform 0.5s ease 0.2s; /* 0.2s delay + 0.5s = 0.7s total */
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.nested-enter-from .outer,
|
|
44
|
+
.nested-leave-to .outer {
|
|
45
|
+
opacity: 0;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.nested-enter-from .inner,
|
|
49
|
+
.nested-leave-to .inner {
|
|
50
|
+
transform: translateX(-30px);
|
|
51
|
+
}
|
|
52
|
+
</style>
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
**Correct Code:**
|
|
56
|
+
```vue
|
|
57
|
+
<template>
|
|
58
|
+
<!-- GOOD: Explicit duration ensures all nested animations complete -->
|
|
59
|
+
<Transition name="nested" :duration="700">
|
|
60
|
+
<div v-if="show" class="outer">
|
|
61
|
+
<div class="inner">Hello</div>
|
|
62
|
+
</div>
|
|
63
|
+
</Transition>
|
|
64
|
+
</template>
|
|
65
|
+
|
|
66
|
+
<style>
|
|
67
|
+
.nested-enter-active .outer,
|
|
68
|
+
.nested-leave-active .outer {
|
|
69
|
+
transition: opacity 0.3s ease;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.nested-enter-active .inner,
|
|
73
|
+
.nested-leave-active .inner {
|
|
74
|
+
/* Now this animation completes fully */
|
|
75
|
+
transition: transform 0.5s ease 0.2s;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.nested-enter-from .outer,
|
|
79
|
+
.nested-leave-to .outer {
|
|
80
|
+
opacity: 0;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
.nested-enter-from .inner,
|
|
84
|
+
.nested-leave-to .inner {
|
|
85
|
+
transform: translateX(-30px);
|
|
86
|
+
}
|
|
87
|
+
</style>
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Different Enter and Leave Durations
|
|
91
|
+
|
|
92
|
+
```vue
|
|
93
|
+
<template>
|
|
94
|
+
<!-- GOOD: Separate durations for enter and leave -->
|
|
95
|
+
<Transition
|
|
96
|
+
name="complex"
|
|
97
|
+
:duration="{ enter: 500, leave: 800 }"
|
|
98
|
+
>
|
|
99
|
+
<div v-if="show" class="container">
|
|
100
|
+
<h1 class="title">Title</h1>
|
|
101
|
+
<p class="content">Content with staggered animation</p>
|
|
102
|
+
</div>
|
|
103
|
+
</Transition>
|
|
104
|
+
</template>
|
|
105
|
+
|
|
106
|
+
<style>
|
|
107
|
+
/* Enter: title first, then content */
|
|
108
|
+
.complex-enter-active .title {
|
|
109
|
+
transition: opacity 0.3s ease, transform 0.3s ease;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
.complex-enter-active .content {
|
|
113
|
+
transition: opacity 0.3s ease 0.2s, transform 0.3s ease 0.2s;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/* Leave: content first, then title (reverse order) */
|
|
117
|
+
.complex-leave-active .content {
|
|
118
|
+
transition: opacity 0.3s ease, transform 0.3s ease;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
.complex-leave-active .title {
|
|
122
|
+
transition: opacity 0.5s ease 0.3s, transform 0.5s ease 0.3s;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
.complex-enter-from .title,
|
|
126
|
+
.complex-enter-from .content,
|
|
127
|
+
.complex-leave-to .title,
|
|
128
|
+
.complex-leave-to .content {
|
|
129
|
+
opacity: 0;
|
|
130
|
+
transform: translateY(20px);
|
|
131
|
+
}
|
|
132
|
+
</style>
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
## Choreographed Staggered Animations
|
|
136
|
+
|
|
137
|
+
```vue
|
|
138
|
+
<template>
|
|
139
|
+
<Transition name="stagger" :duration="800">
|
|
140
|
+
<div v-if="show" class="card">
|
|
141
|
+
<img class="card-image" src="..." />
|
|
142
|
+
<h2 class="card-title">Title</h2>
|
|
143
|
+
<p class="card-body">Body text...</p>
|
|
144
|
+
<button class="card-action">Action</button>
|
|
145
|
+
</div>
|
|
146
|
+
</Transition>
|
|
147
|
+
</template>
|
|
148
|
+
|
|
149
|
+
<style>
|
|
150
|
+
/* Staggered entrance: image -> title -> body -> action */
|
|
151
|
+
.stagger-enter-active .card-image { transition: all 0.3s ease; }
|
|
152
|
+
.stagger-enter-active .card-title { transition: all 0.3s ease 0.1s; }
|
|
153
|
+
.stagger-enter-active .card-body { transition: all 0.3s ease 0.2s; }
|
|
154
|
+
.stagger-enter-active .card-action { transition: all 0.3s ease 0.3s; }
|
|
155
|
+
/* Total: 0.3s delay + 0.3s animation = 0.6s, but use 800ms for safety */
|
|
156
|
+
|
|
157
|
+
.stagger-enter-from .card-image,
|
|
158
|
+
.stagger-enter-from .card-title,
|
|
159
|
+
.stagger-enter-from .card-body,
|
|
160
|
+
.stagger-enter-from .card-action {
|
|
161
|
+
opacity: 0;
|
|
162
|
+
transform: translateY(10px);
|
|
163
|
+
}
|
|
164
|
+
</style>
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
## Calculating Duration
|
|
168
|
+
|
|
169
|
+
Use this formula to calculate the correct duration:
|
|
170
|
+
```
|
|
171
|
+
duration = max(delay + animation_duration) for all nested elements
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
Example:
|
|
175
|
+
- Element A: no delay, 300ms duration = 300ms total
|
|
176
|
+
- Element B: 100ms delay, 300ms duration = 400ms total
|
|
177
|
+
- Element C: 200ms delay, 500ms duration = 700ms total
|
|
178
|
+
|
|
179
|
+
**Required `:duration`**: 700 (or slightly higher for safety margin)
|
|
180
|
+
|
|
181
|
+
## Reference
|
|
182
|
+
- [Vue.js Transition - Nested Transitions](https://vuejs.org/guide/built-ins/transition.html#nested-transitions-and-explicit-transition-durations)
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Avoid Scoped Styles in Reusable Transition Components
|
|
3
|
+
impact: MEDIUM
|
|
4
|
+
impactDescription: Scoped styles in transition wrapper components won't apply to slotted content, breaking the transition animation
|
|
5
|
+
type: gotcha
|
|
6
|
+
tags: [vue3, transition, scoped-css, slot, reusable-component]
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Avoid Scoped Styles in Reusable Transition Components
|
|
10
|
+
|
|
11
|
+
**Impact: MEDIUM** - When creating reusable transition wrapper components, using `<style scoped>` will prevent the transition CSS classes from applying to slotted content. Scoped styles only affect elements directly in the component's template, not content passed through slots. Your transition animations will silently fail.
|
|
12
|
+
|
|
13
|
+
## Task Checklist
|
|
14
|
+
|
|
15
|
+
- [ ] In reusable transition components, use `<style>` without `scoped`
|
|
16
|
+
- [ ] Alternatively, use unique class name prefixes to avoid global conflicts
|
|
17
|
+
- [ ] Or use CSS modules with `:global()` for transition classes
|
|
18
|
+
- [ ] Test that transitions work when component is used in different contexts
|
|
19
|
+
|
|
20
|
+
**Problematic Code:**
|
|
21
|
+
```vue
|
|
22
|
+
<!-- MyFadeTransition.vue -->
|
|
23
|
+
<template>
|
|
24
|
+
<Transition name="my-fade">
|
|
25
|
+
<slot />
|
|
26
|
+
</Transition>
|
|
27
|
+
</template>
|
|
28
|
+
|
|
29
|
+
<!-- BAD: Scoped styles won't apply to slot content! -->
|
|
30
|
+
<style scoped>
|
|
31
|
+
.my-fade-enter-active,
|
|
32
|
+
.my-fade-leave-active {
|
|
33
|
+
transition: opacity 0.3s ease;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.my-fade-enter-from,
|
|
37
|
+
.my-fade-leave-to {
|
|
38
|
+
opacity: 0;
|
|
39
|
+
}
|
|
40
|
+
</style>
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
```vue
|
|
44
|
+
<!-- Parent component using the transition -->
|
|
45
|
+
<template>
|
|
46
|
+
<MyFadeTransition>
|
|
47
|
+
<div v-if="show">This won't animate!</div>
|
|
48
|
+
</MyFadeTransition>
|
|
49
|
+
</template>
|
|
50
|
+
|
|
51
|
+
<!--
|
|
52
|
+
The <div> is slotted content, so .my-fade-* classes
|
|
53
|
+
applied by Vue won't match the scoped CSS selectors
|
|
54
|
+
-->
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
**Correct Code:**
|
|
58
|
+
```vue
|
|
59
|
+
<!-- MyFadeTransition.vue -->
|
|
60
|
+
<template>
|
|
61
|
+
<Transition name="my-fade">
|
|
62
|
+
<slot />
|
|
63
|
+
</Transition>
|
|
64
|
+
</template>
|
|
65
|
+
|
|
66
|
+
<!-- GOOD: Unscoped styles apply to any element -->
|
|
67
|
+
<style>
|
|
68
|
+
.my-fade-enter-active,
|
|
69
|
+
.my-fade-leave-active {
|
|
70
|
+
transition: opacity 0.3s ease;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.my-fade-enter-from,
|
|
74
|
+
.my-fade-leave-to {
|
|
75
|
+
opacity: 0;
|
|
76
|
+
}
|
|
77
|
+
</style>
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## Alternative: Use Unique Prefixed Class Names
|
|
81
|
+
|
|
82
|
+
To avoid global style conflicts, use distinctive prefixes:
|
|
83
|
+
|
|
84
|
+
```vue
|
|
85
|
+
<!-- FadeTransition.vue -->
|
|
86
|
+
<template>
|
|
87
|
+
<Transition name="v-fade-transition">
|
|
88
|
+
<slot />
|
|
89
|
+
</Transition>
|
|
90
|
+
</template>
|
|
91
|
+
|
|
92
|
+
<style>
|
|
93
|
+
/* Unique prefix reduces collision risk */
|
|
94
|
+
.v-fade-transition-enter-active,
|
|
95
|
+
.v-fade-transition-leave-active {
|
|
96
|
+
transition: opacity 0.3s ease;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
.v-fade-transition-enter-from,
|
|
100
|
+
.v-fade-transition-leave-to {
|
|
101
|
+
opacity: 0;
|
|
102
|
+
}
|
|
103
|
+
</style>
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## Alternative: CSS Modules with :global()
|
|
107
|
+
|
|
108
|
+
```vue
|
|
109
|
+
<!-- FadeTransition.vue -->
|
|
110
|
+
<template>
|
|
111
|
+
<Transition name="fade">
|
|
112
|
+
<slot />
|
|
113
|
+
</Transition>
|
|
114
|
+
</template>
|
|
115
|
+
|
|
116
|
+
<style module>
|
|
117
|
+
/* Use :global() for transition classes */
|
|
118
|
+
:global(.fade-enter-active),
|
|
119
|
+
:global(.fade-leave-active) {
|
|
120
|
+
transition: opacity 0.3s ease;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
:global(.fade-enter-from),
|
|
124
|
+
:global(.fade-leave-to) {
|
|
125
|
+
opacity: 0;
|
|
126
|
+
}
|
|
127
|
+
</style>
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
## Alternative: Custom Transition Classes
|
|
131
|
+
|
|
132
|
+
Use the custom class props to apply scoped classes:
|
|
133
|
+
|
|
134
|
+
```vue
|
|
135
|
+
<!-- FadeTransition.vue -->
|
|
136
|
+
<template>
|
|
137
|
+
<Transition
|
|
138
|
+
:enter-active-class="$style.enterActive"
|
|
139
|
+
:leave-active-class="$style.leaveActive"
|
|
140
|
+
:enter-from-class="$style.enterFrom"
|
|
141
|
+
:leave-to-class="$style.leaveTo"
|
|
142
|
+
>
|
|
143
|
+
<slot />
|
|
144
|
+
</Transition>
|
|
145
|
+
</template>
|
|
146
|
+
|
|
147
|
+
<style module>
|
|
148
|
+
.enterActive,
|
|
149
|
+
.leaveActive {
|
|
150
|
+
transition: opacity 0.3s ease;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
.enterFrom,
|
|
154
|
+
.leaveTo {
|
|
155
|
+
opacity: 0;
|
|
156
|
+
}
|
|
157
|
+
</style>
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
## Complete Reusable Transition Component Example
|
|
161
|
+
|
|
162
|
+
```vue
|
|
163
|
+
<!-- transitions/SlideTransition.vue -->
|
|
164
|
+
<template>
|
|
165
|
+
<Transition
|
|
166
|
+
name="slide"
|
|
167
|
+
:mode="mode"
|
|
168
|
+
:appear="appear"
|
|
169
|
+
@before-enter="$emit('before-enter', $event)"
|
|
170
|
+
@enter="$emit('enter', $event)"
|
|
171
|
+
@after-enter="$emit('after-enter', $event)"
|
|
172
|
+
@before-leave="$emit('before-leave', $event)"
|
|
173
|
+
@leave="$emit('leave', $event)"
|
|
174
|
+
@after-leave="$emit('after-leave', $event)"
|
|
175
|
+
>
|
|
176
|
+
<slot />
|
|
177
|
+
</Transition>
|
|
178
|
+
</template>
|
|
179
|
+
|
|
180
|
+
<script setup>
|
|
181
|
+
defineProps({
|
|
182
|
+
mode: {
|
|
183
|
+
type: String,
|
|
184
|
+
default: 'out-in',
|
|
185
|
+
validator: (v) => ['out-in', 'in-out', ''].includes(v)
|
|
186
|
+
},
|
|
187
|
+
appear: {
|
|
188
|
+
type: Boolean,
|
|
189
|
+
default: false
|
|
190
|
+
}
|
|
191
|
+
})
|
|
192
|
+
|
|
193
|
+
defineEmits([
|
|
194
|
+
'before-enter', 'enter', 'after-enter',
|
|
195
|
+
'before-leave', 'leave', 'after-leave'
|
|
196
|
+
])
|
|
197
|
+
</script>
|
|
198
|
+
|
|
199
|
+
<!-- Unscoped so styles apply to slotted content -->
|
|
200
|
+
<style>
|
|
201
|
+
.slide-enter-active,
|
|
202
|
+
.slide-leave-active {
|
|
203
|
+
transition: transform 0.3s ease, opacity 0.3s ease;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
.slide-enter-from {
|
|
207
|
+
opacity: 0;
|
|
208
|
+
transform: translateX(-20px);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
.slide-leave-to {
|
|
212
|
+
opacity: 0;
|
|
213
|
+
transform: translateX(20px);
|
|
214
|
+
}
|
|
215
|
+
</style>
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
Usage:
|
|
219
|
+
```vue
|
|
220
|
+
<template>
|
|
221
|
+
<SlideTransition>
|
|
222
|
+
<div v-if="show" class="content">
|
|
223
|
+
This will properly animate!
|
|
224
|
+
</div>
|
|
225
|
+
</SlideTransition>
|
|
226
|
+
</template>
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
## Why This Happens
|
|
230
|
+
|
|
231
|
+
Vue's scoped styles work by adding a unique data attribute (e.g., `data-v-7ba5bd90`) to elements and selectors:
|
|
232
|
+
|
|
233
|
+
```css
|
|
234
|
+
/* What you write */
|
|
235
|
+
.my-fade-enter-active { ... }
|
|
236
|
+
|
|
237
|
+
/* What Vue generates (scoped) */
|
|
238
|
+
.my-fade-enter-active[data-v-7ba5bd90] { ... }
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
Slotted content comes from the parent component and gets the parent's data attribute, not the transition component's attribute. So the selectors never match.
|
|
242
|
+
|
|
243
|
+
## Reference
|
|
244
|
+
- [Vue.js Reusable Transitions](https://vuejs.org/guide/built-ins/transition.html#reusable-transitions)
|
|
245
|
+
- [Vue.js Scoped CSS](https://vuejs.org/api/sfc-css-features.html#scoped-css)
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: RouterView Transitions Always Apply Despite Missing appear Prop
|
|
3
|
+
impact: LOW
|
|
4
|
+
impactDescription: Initial page load with RouterView triggers transition animation even without the appear prop due to async navigation
|
|
5
|
+
type: gotcha
|
|
6
|
+
tags: [vue3, transition, vue-router, appear, initial-load, navigation]
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# RouterView Transitions Always Apply Despite Missing appear Prop
|
|
10
|
+
|
|
11
|
+
**Impact: LOW** - When using `<Transition>` with Vue Router's `<RouterView>`, the enter transition animation runs on initial page load even if you haven't added the `appear` prop. This differs from normal Transition behavior where `appear` is required for initial render animations. This happens because Vue Router's navigations are asynchronous, causing the component to mount after the initial render.
|
|
12
|
+
|
|
13
|
+
## Task Checklist
|
|
14
|
+
|
|
15
|
+
- [ ] Be aware that RouterView transitions always animate on initial load
|
|
16
|
+
- [ ] If you want NO animation on initial load, you need to handle this explicitly
|
|
17
|
+
- [ ] Don't add `appear` prop expecting it to change behavior - it's already effectively enabled
|
|
18
|
+
- [ ] Consider whether initial animation is desired for your UX
|
|
19
|
+
|
|
20
|
+
**Expected Behavior (Normal Transition):**
|
|
21
|
+
```vue
|
|
22
|
+
<template>
|
|
23
|
+
<!-- Without appear: No animation on initial render -->
|
|
24
|
+
<Transition name="fade">
|
|
25
|
+
<div v-if="show">Content</div>
|
|
26
|
+
</Transition>
|
|
27
|
+
|
|
28
|
+
<!-- With appear: Animates on initial render -->
|
|
29
|
+
<Transition name="fade" appear>
|
|
30
|
+
<div v-if="show">Content</div>
|
|
31
|
+
</Transition>
|
|
32
|
+
</template>
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
**RouterView Behavior (Different!):**
|
|
36
|
+
```vue
|
|
37
|
+
<template>
|
|
38
|
+
<!-- RouterView transitions ALWAYS animate on initial load -->
|
|
39
|
+
<!-- The appear prop has no effect here -->
|
|
40
|
+
<RouterView v-slot="{ Component }">
|
|
41
|
+
<Transition name="fade">
|
|
42
|
+
<component :is="Component" />
|
|
43
|
+
</Transition>
|
|
44
|
+
</RouterView>
|
|
45
|
+
</template>
|
|
46
|
+
|
|
47
|
+
<!--
|
|
48
|
+
On initial page load:
|
|
49
|
+
1. Vue renders the app
|
|
50
|
+
2. Router resolves the route (async)
|
|
51
|
+
3. Component mounts AFTER initial render
|
|
52
|
+
4. Enter transition triggers (as if toggled from v-if="false" to v-if="true")
|
|
53
|
+
-->
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Why This Happens
|
|
57
|
+
|
|
58
|
+
Vue Router navigations are asynchronous. The sequence is:
|
|
59
|
+
|
|
60
|
+
1. Vue application mounts with empty RouterView
|
|
61
|
+
2. Router resolves the initial route
|
|
62
|
+
3. Route component is inserted into RouterView
|
|
63
|
+
4. This insertion triggers the enter transition
|
|
64
|
+
|
|
65
|
+
Since the component wasn't present in the initial render and is "inserted" afterward, Vue treats it as a normal enter transition, not an initial render.
|
|
66
|
+
|
|
67
|
+
## If You Want to Disable Initial Animation
|
|
68
|
+
|
|
69
|
+
```vue
|
|
70
|
+
<template>
|
|
71
|
+
<RouterView v-slot="{ Component }">
|
|
72
|
+
<Transition :name="isInitialLoad ? '' : 'fade'" mode="out-in">
|
|
73
|
+
<component :is="Component" />
|
|
74
|
+
</Transition>
|
|
75
|
+
</RouterView>
|
|
76
|
+
</template>
|
|
77
|
+
|
|
78
|
+
<script setup>
|
|
79
|
+
import { ref, onMounted } from 'vue'
|
|
80
|
+
import { useRouter } from 'vue-router'
|
|
81
|
+
|
|
82
|
+
const isInitialLoad = ref(true)
|
|
83
|
+
const router = useRouter()
|
|
84
|
+
|
|
85
|
+
// After first navigation completes, enable transitions
|
|
86
|
+
router.isReady().then(() => {
|
|
87
|
+
// Small delay to ensure initial render is complete
|
|
88
|
+
setTimeout(() => {
|
|
89
|
+
isInitialLoad.value = false
|
|
90
|
+
}, 0)
|
|
91
|
+
})
|
|
92
|
+
</script>
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Alternative: Use CSS to Skip First Animation
|
|
96
|
+
|
|
97
|
+
```vue
|
|
98
|
+
<template>
|
|
99
|
+
<RouterView v-slot="{ Component }">
|
|
100
|
+
<Transition name="fade" mode="out-in">
|
|
101
|
+
<component :is="Component" :class="{ 'skip-initial': isInitialLoad }" />
|
|
102
|
+
</Transition>
|
|
103
|
+
</RouterView>
|
|
104
|
+
</template>
|
|
105
|
+
|
|
106
|
+
<script setup>
|
|
107
|
+
import { ref } from 'vue'
|
|
108
|
+
import { useRouter } from 'vue-router'
|
|
109
|
+
|
|
110
|
+
const isInitialLoad = ref(true)
|
|
111
|
+
const router = useRouter()
|
|
112
|
+
|
|
113
|
+
router.isReady().then(() => {
|
|
114
|
+
isInitialLoad.value = false
|
|
115
|
+
})
|
|
116
|
+
</script>
|
|
117
|
+
|
|
118
|
+
<style>
|
|
119
|
+
.fade-enter-active,
|
|
120
|
+
.fade-leave-active {
|
|
121
|
+
transition: opacity 0.3s ease;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
.fade-enter-from,
|
|
125
|
+
.fade-leave-to {
|
|
126
|
+
opacity: 0;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/* Skip animation on initial load */
|
|
130
|
+
.skip-initial.fade-enter-active {
|
|
131
|
+
transition: none;
|
|
132
|
+
}
|
|
133
|
+
</style>
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
## Standard RouterView Transition Pattern
|
|
137
|
+
|
|
138
|
+
If you're fine with initial animation (often desired), use the standard pattern:
|
|
139
|
+
|
|
140
|
+
```vue
|
|
141
|
+
<template>
|
|
142
|
+
<RouterView v-slot="{ Component, route }">
|
|
143
|
+
<Transition :name="route.meta.transition || 'fade'" mode="out-in">
|
|
144
|
+
<component :is="Component" :key="route.path" />
|
|
145
|
+
</Transition>
|
|
146
|
+
</RouterView>
|
|
147
|
+
</template>
|
|
148
|
+
|
|
149
|
+
<style>
|
|
150
|
+
.fade-enter-active,
|
|
151
|
+
.fade-leave-active {
|
|
152
|
+
transition: opacity 0.3s ease;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
.fade-enter-from,
|
|
156
|
+
.fade-leave-to {
|
|
157
|
+
opacity: 0;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/* Route-specific transitions via meta */
|
|
161
|
+
.slide-enter-active,
|
|
162
|
+
.slide-leave-active {
|
|
163
|
+
transition: transform 0.3s ease;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
.slide-enter-from {
|
|
167
|
+
transform: translateX(100%);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
.slide-leave-to {
|
|
171
|
+
transform: translateX(-100%);
|
|
172
|
+
}
|
|
173
|
+
</style>
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
```javascript
|
|
177
|
+
// router.js
|
|
178
|
+
const routes = [
|
|
179
|
+
{
|
|
180
|
+
path: '/',
|
|
181
|
+
component: Home
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
path: '/about',
|
|
185
|
+
component: About,
|
|
186
|
+
meta: { transition: 'slide' } // Custom transition for this route
|
|
187
|
+
}
|
|
188
|
+
]
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
## Reference
|
|
192
|
+
- [Vue Router Transitions](https://router.vuejs.org/guide/advanced/transitions.html)
|
|
193
|
+
- [Vue.js Transition appear](https://vuejs.org/guide/built-ins/transition.html#transition-on-appear)
|