mdk-skills 2.2.2 → 2.2.3
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/.claude/.install.log +9 -40
- package/.claude/backups/20260510.151953/.install.log +1 -0
- package/package.json +1 -1
- package/scripts/web-ui/server.js +8 -1
- package/.claude/backups/20260510.145547/.install.log +0 -18
- package/.claude/backups/20260510.145547/settings.json +0 -35
- package/.claude/backups/20260510.145547/skills/test1/.meta.json +0 -6
- package/.claude/backups/20260510.145547/skills/test2/.meta.json +0 -6
- package/.claude/backups/20260510.145651/.install.log +0 -19
- package/.claude/backups/20260510.145651/profiles.json +0 -67
- package/.claude/backups/20260510.145651/skills/frontend-code-review/.meta.json +0 -6
- package/.claude/backups/20260510.145651/skills/frontend-code-review/SKILL.md +0 -167
- package/.claude/backups/20260510.145651/skills/frontend-code-review/references/checklist.md +0 -298
- package/.claude/backups/20260510.145651/skills/frontend-design/.meta.json +0 -6
- package/.claude/backups/20260510.145651/skills/frontend-design/LICENSE.txt +0 -177
- package/.claude/backups/20260510.145651/skills/frontend-design/SKILL.md +0 -42
- package/.claude/backups/20260510.145651/skills/skill-creator/.meta.json +0 -6
- package/.claude/backups/20260510.145651/skills/skill-creator/SKILL.md +0 -356
- package/.claude/backups/20260510.145651/skills/skill-creator/references/output-patterns.md +0 -82
- package/.claude/backups/20260510.145651/skills/skill-creator/references/workflows.md +0 -28
- package/.claude/backups/20260510.145651/skills/skill-creator/scripts/init_skill.py +0 -303
- package/.claude/backups/20260510.145651/skills/skill-creator/scripts/package_skill.py +0 -110
- package/.claude/backups/20260510.145651/skills/skill-creator/scripts/quick_validate.py +0 -95
- package/.claude/backups/20260510.145651/skills/test1/.meta.json +0 -6
- package/.claude/backups/20260510.145651/skills/test2/.meta.json +0 -6
- package/.claude/backups/20260510.145651/skills/ui-ux-pro-max/.meta.json +0 -6
- package/.claude/backups/20260510.145651/skills/ui-ux-pro-max/SKILL.md +0 -228
- package/.claude/backups/20260510.145651/skills/ui-ux-pro-max/data/charts.csv +0 -26
- package/.claude/backups/20260510.145651/skills/ui-ux-pro-max/data/colors.csv +0 -97
- package/.claude/backups/20260510.145651/skills/ui-ux-pro-max/data/landing.csv +0 -31
- package/.claude/backups/20260510.145651/skills/ui-ux-pro-max/data/products.csv +0 -97
- package/.claude/backups/20260510.145651/skills/ui-ux-pro-max/data/prompts.csv +0 -24
- package/.claude/backups/20260510.145651/skills/ui-ux-pro-max/data/stacks/flutter.csv +0 -53
- package/.claude/backups/20260510.145651/skills/ui-ux-pro-max/data/stacks/html-tailwind.csv +0 -56
- package/.claude/backups/20260510.145651/skills/ui-ux-pro-max/data/stacks/nextjs.csv +0 -53
- package/.claude/backups/20260510.145651/skills/ui-ux-pro-max/data/stacks/nuxt-ui.csv +0 -51
- package/.claude/backups/20260510.145651/skills/ui-ux-pro-max/data/stacks/nuxtjs.csv +0 -59
- package/.claude/backups/20260510.145651/skills/ui-ux-pro-max/data/stacks/react-native.csv +0 -52
- package/.claude/backups/20260510.145651/skills/ui-ux-pro-max/data/stacks/react.csv +0 -54
- package/.claude/backups/20260510.145651/skills/ui-ux-pro-max/data/stacks/svelte.csv +0 -54
- package/.claude/backups/20260510.145651/skills/ui-ux-pro-max/data/stacks/swiftui.csv +0 -51
- package/.claude/backups/20260510.145651/skills/ui-ux-pro-max/data/stacks/vue.csv +0 -50
- package/.claude/backups/20260510.145651/skills/ui-ux-pro-max/data/styles.csv +0 -59
- package/.claude/backups/20260510.145651/skills/ui-ux-pro-max/data/typography.csv +0 -58
- package/.claude/backups/20260510.145651/skills/ui-ux-pro-max/data/ux-guidelines.csv +0 -100
- package/.claude/backups/20260510.145651/skills/ui-ux-pro-max/scripts/core.py +0 -238
- package/.claude/backups/20260510.145651/skills/ui-ux-pro-max/scripts/search.py +0 -61
- package/.claude/backups/20260510.145651/skills/v3-fe-biz-patterns/.meta.json +0 -6
- package/.claude/backups/20260510.145651/skills/v3-fe-biz-patterns/SKILL.md +0 -26
- package/.claude/backups/20260510.145651/skills/v3-fe-biz-patterns/references/infinite-scroll.md +0 -292
- package/.claude/backups/20260510.145651/skills/v3-fe-biz-patterns/references/pinia-store.md +0 -174
- package/.claude/backups/20260510.145651/skills/v3-fe-biz-patterns/references/service-layer.md +0 -198
- package/.claude/backups/20260510.145651/skills/v3-fe-biz-patterns/references/tab-anchor.md +0 -1125
- package/.claude/backups/20260510.145651/skills/v3-fe-biz-patterns/references/use-loading.md +0 -114
- package/.claude/backups/20260510.145651/skills/vue/.meta.json +0 -6
- package/.claude/backups/20260510.145651/skills/vue/SKILL.md +0 -103
- package/.claude/backups/20260510.145651/skills/vue/references/components.md +0 -323
- package/.claude/backups/20260510.145651/skills/vue/references/composables.md +0 -358
- package/.claude/backups/20260510.145651/skills/vue/references/directives.md +0 -225
- package/.claude/backups/20260510.145651/skills/vue/references/gotchas.md +0 -438
- package/.claude/backups/20260510.145651/skills/vue/references/provide-inject.md +0 -174
- package/.claude/backups/20260510.145651/skills/vue/references/reactivity.md +0 -289
- package/.claude/backups/20260510.145651/skills/vue/references/router.md +0 -181
- package/.claude/backups/20260510.145651/skills/vue/references/testing.md +0 -294
- package/.claude/backups/20260510.145651/skills/vue/references/typescript.md +0 -172
- package/.claude/backups/20260510.145651/skills/vue/references/utils-client.md +0 -156
- package/.claude/backups/20260510.150310/.install.log +0 -30
- package/.claude/backups/20260510.150310/profiles.json +0 -67
- package/.claude/backups/20260510.150310/settings.json +0 -29
- package/.claude/backups/20260510.150310/skills/frontend-code-review/.meta.json +0 -6
- package/.claude/backups/20260510.150310/skills/frontend-code-review/SKILL.md +0 -167
- package/.claude/backups/20260510.150310/skills/frontend-code-review/references/checklist.md +0 -298
- package/.claude/backups/20260510.150310/skills/frontend-design/.meta.json +0 -6
- package/.claude/backups/20260510.150310/skills/frontend-design/LICENSE.txt +0 -177
- package/.claude/backups/20260510.150310/skills/frontend-design/SKILL.md +0 -42
- package/.claude/backups/20260510.150310/skills/skill-creator/.meta.json +0 -6
- package/.claude/backups/20260510.150310/skills/skill-creator/SKILL.md +0 -356
- package/.claude/backups/20260510.150310/skills/skill-creator/references/output-patterns.md +0 -82
- package/.claude/backups/20260510.150310/skills/skill-creator/references/workflows.md +0 -28
- package/.claude/backups/20260510.150310/skills/skill-creator/scripts/init_skill.py +0 -303
- package/.claude/backups/20260510.150310/skills/skill-creator/scripts/package_skill.py +0 -110
- package/.claude/backups/20260510.150310/skills/skill-creator/scripts/quick_validate.py +0 -95
- package/.claude/backups/20260510.150310/skills/test1/.meta.json +0 -6
- package/.claude/backups/20260510.150310/skills/test2/.meta.json +0 -6
- package/.claude/backups/20260510.150310/skills/ui-ux-pro-max/.meta.json +0 -6
- package/.claude/backups/20260510.150310/skills/ui-ux-pro-max/SKILL.md +0 -228
- package/.claude/backups/20260510.150310/skills/ui-ux-pro-max/data/charts.csv +0 -26
- package/.claude/backups/20260510.150310/skills/ui-ux-pro-max/data/colors.csv +0 -97
- package/.claude/backups/20260510.150310/skills/ui-ux-pro-max/data/landing.csv +0 -31
- package/.claude/backups/20260510.150310/skills/ui-ux-pro-max/data/products.csv +0 -97
- package/.claude/backups/20260510.150310/skills/ui-ux-pro-max/data/prompts.csv +0 -24
- package/.claude/backups/20260510.150310/skills/ui-ux-pro-max/data/stacks/flutter.csv +0 -53
- package/.claude/backups/20260510.150310/skills/ui-ux-pro-max/data/stacks/html-tailwind.csv +0 -56
- package/.claude/backups/20260510.150310/skills/ui-ux-pro-max/data/stacks/nextjs.csv +0 -53
- package/.claude/backups/20260510.150310/skills/ui-ux-pro-max/data/stacks/nuxt-ui.csv +0 -51
- package/.claude/backups/20260510.150310/skills/ui-ux-pro-max/data/stacks/nuxtjs.csv +0 -59
- package/.claude/backups/20260510.150310/skills/ui-ux-pro-max/data/stacks/react-native.csv +0 -52
- package/.claude/backups/20260510.150310/skills/ui-ux-pro-max/data/stacks/react.csv +0 -54
- package/.claude/backups/20260510.150310/skills/ui-ux-pro-max/data/stacks/svelte.csv +0 -54
- package/.claude/backups/20260510.150310/skills/ui-ux-pro-max/data/stacks/swiftui.csv +0 -51
- package/.claude/backups/20260510.150310/skills/ui-ux-pro-max/data/stacks/vue.csv +0 -50
- package/.claude/backups/20260510.150310/skills/ui-ux-pro-max/data/styles.csv +0 -59
- package/.claude/backups/20260510.150310/skills/ui-ux-pro-max/data/typography.csv +0 -58
- package/.claude/backups/20260510.150310/skills/ui-ux-pro-max/data/ux-guidelines.csv +0 -100
- package/.claude/backups/20260510.150310/skills/ui-ux-pro-max/scripts/core.py +0 -238
- package/.claude/backups/20260510.150310/skills/ui-ux-pro-max/scripts/search.py +0 -61
- package/.claude/backups/20260510.150310/skills/v3-fe-biz-patterns/.meta.json +0 -6
- package/.claude/backups/20260510.150310/skills/v3-fe-biz-patterns/SKILL.md +0 -26
- package/.claude/backups/20260510.150310/skills/v3-fe-biz-patterns/references/infinite-scroll.md +0 -292
- package/.claude/backups/20260510.150310/skills/v3-fe-biz-patterns/references/pinia-store.md +0 -174
- package/.claude/backups/20260510.150310/skills/v3-fe-biz-patterns/references/service-layer.md +0 -198
- package/.claude/backups/20260510.150310/skills/v3-fe-biz-patterns/references/tab-anchor.md +0 -1125
- package/.claude/backups/20260510.150310/skills/v3-fe-biz-patterns/references/use-loading.md +0 -114
- package/.claude/backups/20260510.150310/skills/vue/.meta.json +0 -6
- package/.claude/backups/20260510.150310/skills/vue/SKILL.md +0 -103
- package/.claude/backups/20260510.150310/skills/vue/references/components.md +0 -323
- package/.claude/backups/20260510.150310/skills/vue/references/composables.md +0 -358
- package/.claude/backups/20260510.150310/skills/vue/references/directives.md +0 -225
- package/.claude/backups/20260510.150310/skills/vue/references/gotchas.md +0 -438
- package/.claude/backups/20260510.150310/skills/vue/references/provide-inject.md +0 -174
- package/.claude/backups/20260510.150310/skills/vue/references/reactivity.md +0 -289
- package/.claude/backups/20260510.150310/skills/vue/references/router.md +0 -181
- package/.claude/backups/20260510.150310/skills/vue/references/testing.md +0 -294
- package/.claude/backups/20260510.150310/skills/vue/references/typescript.md +0 -172
- package/.claude/backups/20260510.150310/skills/vue/references/utils-client.md +0 -156
- package/.claude/backups/CLAUDE.md.20260510.145155 +0 -131
- package/.claude/backups/CLAUDE.md.20260510.145651 +0 -131
- package/.claude/backups/CLAUDE.md.20260510.150310 +0 -131
- package/.claude/skills/test1/.meta.json +0 -6
- package/.claude/skills/test2/.meta.json +0 -6
- /package/.claude/backups/{20260510.145547 → 20260510.151953}/profiles.json +0 -0
- /package/.claude/backups/{20260510.145651 → 20260510.151953}/settings.json +0 -0
- /package/.claude/backups/{20260510.145547 → 20260510.151953}/skills/frontend-code-review/.meta.json +0 -0
- /package/.claude/backups/{20260510.145547 → 20260510.151953}/skills/frontend-code-review/SKILL.md +0 -0
- /package/.claude/backups/{20260510.145547 → 20260510.151953}/skills/frontend-code-review/references/checklist.md +0 -0
- /package/.claude/backups/{20260510.145547 → 20260510.151953}/skills/frontend-design/.meta.json +0 -0
- /package/.claude/backups/{20260510.145547 → 20260510.151953}/skills/frontend-design/LICENSE.txt +0 -0
- /package/.claude/backups/{20260510.145547 → 20260510.151953}/skills/frontend-design/SKILL.md +0 -0
- /package/.claude/backups/{20260510.145547 → 20260510.151953}/skills/skill-creator/.meta.json +0 -0
- /package/.claude/backups/{20260510.145547 → 20260510.151953}/skills/skill-creator/SKILL.md +0 -0
- /package/.claude/backups/{20260510.145547 → 20260510.151953}/skills/skill-creator/references/output-patterns.md +0 -0
- /package/.claude/backups/{20260510.145547 → 20260510.151953}/skills/skill-creator/references/workflows.md +0 -0
- /package/.claude/backups/{20260510.145547 → 20260510.151953}/skills/skill-creator/scripts/init_skill.py +0 -0
- /package/.claude/backups/{20260510.145547 → 20260510.151953}/skills/skill-creator/scripts/package_skill.py +0 -0
- /package/.claude/backups/{20260510.145547 → 20260510.151953}/skills/skill-creator/scripts/quick_validate.py +0 -0
- /package/.claude/backups/{20260510.145547 → 20260510.151953}/skills/ui-ux-pro-max/.meta.json +0 -0
- /package/.claude/backups/{20260510.145547 → 20260510.151953}/skills/ui-ux-pro-max/SKILL.md +0 -0
- /package/.claude/backups/{20260510.145547 → 20260510.151953}/skills/ui-ux-pro-max/data/charts.csv +0 -0
- /package/.claude/backups/{20260510.145547 → 20260510.151953}/skills/ui-ux-pro-max/data/colors.csv +0 -0
- /package/.claude/backups/{20260510.145547 → 20260510.151953}/skills/ui-ux-pro-max/data/landing.csv +0 -0
- /package/.claude/backups/{20260510.145547 → 20260510.151953}/skills/ui-ux-pro-max/data/products.csv +0 -0
- /package/.claude/backups/{20260510.145547 → 20260510.151953}/skills/ui-ux-pro-max/data/prompts.csv +0 -0
- /package/.claude/backups/{20260510.145547 → 20260510.151953}/skills/ui-ux-pro-max/data/stacks/flutter.csv +0 -0
- /package/.claude/backups/{20260510.145547 → 20260510.151953}/skills/ui-ux-pro-max/data/stacks/html-tailwind.csv +0 -0
- /package/.claude/backups/{20260510.145547 → 20260510.151953}/skills/ui-ux-pro-max/data/stacks/nextjs.csv +0 -0
- /package/.claude/backups/{20260510.145547 → 20260510.151953}/skills/ui-ux-pro-max/data/stacks/nuxt-ui.csv +0 -0
- /package/.claude/backups/{20260510.145547 → 20260510.151953}/skills/ui-ux-pro-max/data/stacks/nuxtjs.csv +0 -0
- /package/.claude/backups/{20260510.145547 → 20260510.151953}/skills/ui-ux-pro-max/data/stacks/react-native.csv +0 -0
- /package/.claude/backups/{20260510.145547 → 20260510.151953}/skills/ui-ux-pro-max/data/stacks/react.csv +0 -0
- /package/.claude/backups/{20260510.145547 → 20260510.151953}/skills/ui-ux-pro-max/data/stacks/svelte.csv +0 -0
- /package/.claude/backups/{20260510.145547 → 20260510.151953}/skills/ui-ux-pro-max/data/stacks/swiftui.csv +0 -0
- /package/.claude/backups/{20260510.145547 → 20260510.151953}/skills/ui-ux-pro-max/data/stacks/vue.csv +0 -0
- /package/.claude/backups/{20260510.145547 → 20260510.151953}/skills/ui-ux-pro-max/data/styles.csv +0 -0
- /package/.claude/backups/{20260510.145547 → 20260510.151953}/skills/ui-ux-pro-max/data/typography.csv +0 -0
- /package/.claude/backups/{20260510.145547 → 20260510.151953}/skills/ui-ux-pro-max/data/ux-guidelines.csv +0 -0
- /package/.claude/backups/{20260510.145547 → 20260510.151953}/skills/ui-ux-pro-max/scripts/core.py +0 -0
- /package/.claude/backups/{20260510.145547 → 20260510.151953}/skills/ui-ux-pro-max/scripts/search.py +0 -0
- /package/.claude/backups/{20260510.145547 → 20260510.151953}/skills/v3-fe-biz-patterns/.meta.json +0 -0
- /package/.claude/backups/{20260510.145547 → 20260510.151953}/skills/v3-fe-biz-patterns/SKILL.md +0 -0
- /package/.claude/backups/{20260510.145547 → 20260510.151953}/skills/v3-fe-biz-patterns/references/infinite-scroll.md +0 -0
- /package/.claude/backups/{20260510.145547 → 20260510.151953}/skills/v3-fe-biz-patterns/references/pinia-store.md +0 -0
- /package/.claude/backups/{20260510.145547 → 20260510.151953}/skills/v3-fe-biz-patterns/references/service-layer.md +0 -0
- /package/.claude/backups/{20260510.145547 → 20260510.151953}/skills/v3-fe-biz-patterns/references/tab-anchor.md +0 -0
- /package/.claude/backups/{20260510.145547 → 20260510.151953}/skills/v3-fe-biz-patterns/references/use-loading.md +0 -0
- /package/.claude/backups/{20260510.145547 → 20260510.151953}/skills/vue/.meta.json +0 -0
- /package/.claude/backups/{20260510.145547 → 20260510.151953}/skills/vue/SKILL.md +0 -0
- /package/.claude/backups/{20260510.145547 → 20260510.151953}/skills/vue/references/components.md +0 -0
- /package/.claude/backups/{20260510.145547 → 20260510.151953}/skills/vue/references/composables.md +0 -0
- /package/.claude/backups/{20260510.145547 → 20260510.151953}/skills/vue/references/directives.md +0 -0
- /package/.claude/backups/{20260510.145547 → 20260510.151953}/skills/vue/references/gotchas.md +0 -0
- /package/.claude/backups/{20260510.145547 → 20260510.151953}/skills/vue/references/provide-inject.md +0 -0
- /package/.claude/backups/{20260510.145547 → 20260510.151953}/skills/vue/references/reactivity.md +0 -0
- /package/.claude/backups/{20260510.145547 → 20260510.151953}/skills/vue/references/router.md +0 -0
- /package/.claude/backups/{20260510.145547 → 20260510.151953}/skills/vue/references/testing.md +0 -0
- /package/.claude/backups/{20260510.145547 → 20260510.151953}/skills/vue/references/typescript.md +0 -0
- /package/.claude/backups/{20260510.145547 → 20260510.151953}/skills/vue/references/utils-client.md +0 -0
- /package/.claude/backups/{CLAUDE.md.20260510.144501 → CLAUDE.md.20260510.151953} +0 -0
|
@@ -1,358 +0,0 @@
|
|
|
1
|
-
# Vue Composables
|
|
2
|
-
|
|
3
|
-
Reusable functions encapsulating stateful logic using Composition API.
|
|
4
|
-
|
|
5
|
-
## Core Rules
|
|
6
|
-
|
|
7
|
-
1. **VueUse first** - check [vueuse.org](https://vueuse.org) before writing custom
|
|
8
|
-
2. **No async composables** - lose lifecycle context when awaited in other composables
|
|
9
|
-
3. **Top-level only** - never call in event handlers, conditionals, or loops
|
|
10
|
-
4. **readonly() exports** - protect internal state from external mutation
|
|
11
|
-
5. **useState() for SSR** - use Nuxt's `useState()` not global refs
|
|
12
|
-
|
|
13
|
-
## Quick Reference
|
|
14
|
-
|
|
15
|
-
| Pattern | Example |
|
|
16
|
-
| --------- | ------------------------------------------------ |
|
|
17
|
-
| Naming | `useAuth`, `useCounter`, `useDebounce` |
|
|
18
|
-
| State | `const count = ref(0)` |
|
|
19
|
-
| Computed | `const double = computed(() => count.value * 2)` |
|
|
20
|
-
| Lifecycle | `onMounted(() => ...)`, `onUnmounted(() => ...)` |
|
|
21
|
-
| Return | `return { count, increment }` |
|
|
22
|
-
|
|
23
|
-
## Structure
|
|
24
|
-
|
|
25
|
-
```ts
|
|
26
|
-
// composables/useCounter.ts
|
|
27
|
-
import { readonly, ref } from 'vue'
|
|
28
|
-
|
|
29
|
-
export function useCounter(initialValue = 0) {
|
|
30
|
-
const count = ref(initialValue)
|
|
31
|
-
|
|
32
|
-
function increment() { count.value++ }
|
|
33
|
-
function decrement() { count.value-- }
|
|
34
|
-
function reset() { count.value = initialValue }
|
|
35
|
-
|
|
36
|
-
return {
|
|
37
|
-
count: readonly(count), // readonly if shouldn't be mutated
|
|
38
|
-
increment,
|
|
39
|
-
decrement,
|
|
40
|
-
reset,
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
## Naming
|
|
46
|
-
|
|
47
|
-
**Always prefix with `use`:** `useAuth`, `useLocalStorage`, `useDebounce`
|
|
48
|
-
|
|
49
|
-
**File = function:** `useAuth.ts` exports `useAuth`
|
|
50
|
-
|
|
51
|
-
## Best Practices
|
|
52
|
-
|
|
53
|
-
**Do:**
|
|
54
|
-
|
|
55
|
-
- Return object with named properties (destructuring-friendly)
|
|
56
|
-
- Accept options object for configuration
|
|
57
|
-
- Use `readonly()` for state that shouldn't mutate
|
|
58
|
-
- Handle cleanup (`onUnmounted`, `onScopeDispose`)
|
|
59
|
-
- Add JSDoc for complex functions
|
|
60
|
-
|
|
61
|
-
## Lifecycle
|
|
62
|
-
|
|
63
|
-
Hooks execute in component context:
|
|
64
|
-
|
|
65
|
-
```ts
|
|
66
|
-
export function useEventListener(target: EventTarget, event: string, handler: Function) {
|
|
67
|
-
onMounted(() => target.addEventListener(event, handler))
|
|
68
|
-
onUnmounted(() => target.removeEventListener(event, handler))
|
|
69
|
-
}
|
|
70
|
-
```
|
|
71
|
-
|
|
72
|
-
**Watcher cleanup (Vue 3.5+):**
|
|
73
|
-
|
|
74
|
-
```ts
|
|
75
|
-
import { watch, onWatcherCleanup } from 'vue'
|
|
76
|
-
|
|
77
|
-
export function usePolling(url: Ref<string>) {
|
|
78
|
-
watch(url, (newUrl) => {
|
|
79
|
-
const interval = setInterval(() => {
|
|
80
|
-
fetch(newUrl).then(/* ... */)
|
|
81
|
-
}, 1000)
|
|
82
|
-
|
|
83
|
-
// Cleanup when watcher re-runs or stops
|
|
84
|
-
onWatcherCleanup(() => {
|
|
85
|
-
clearInterval(interval)
|
|
86
|
-
})
|
|
87
|
-
})
|
|
88
|
-
}
|
|
89
|
-
```
|
|
90
|
-
|
|
91
|
-
**Benefits of `onWatcherCleanup()`:**
|
|
92
|
-
|
|
93
|
-
- Cleaner than returning cleanup functions
|
|
94
|
-
- Works with async watchers
|
|
95
|
-
- Can be called multiple times in same watcher
|
|
96
|
-
|
|
97
|
-
## Async Pattern
|
|
98
|
-
|
|
99
|
-
```ts
|
|
100
|
-
export function useAsyncData<T>(fetcher: () => Promise<T>) {
|
|
101
|
-
const data = ref<T | null>(null)
|
|
102
|
-
const error = ref<Error | null>(null)
|
|
103
|
-
const loading = ref(false)
|
|
104
|
-
|
|
105
|
-
async function execute() {
|
|
106
|
-
loading.value = true
|
|
107
|
-
error.value = null
|
|
108
|
-
try {
|
|
109
|
-
data.value = await fetcher()
|
|
110
|
-
}
|
|
111
|
-
catch (e) {
|
|
112
|
-
error.value = e as Error
|
|
113
|
-
}
|
|
114
|
-
finally {
|
|
115
|
-
loading.value = false
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
execute()
|
|
120
|
-
return { data, error, loading, refetch: execute }
|
|
121
|
-
}
|
|
122
|
-
```
|
|
123
|
-
|
|
124
|
-
**Data fetching:** Prefer Pinia Colada queries over custom composables.
|
|
125
|
-
|
|
126
|
-
## VueUse
|
|
127
|
-
|
|
128
|
-
> For VueUse composable reference, use the `vueuse` skill.
|
|
129
|
-
|
|
130
|
-
Check VueUse before writing custom composables - most patterns already implemented.
|
|
131
|
-
|
|
132
|
-
> **For Nuxt-specific composables** (useFetch, useRequestURL): see `nuxt` skill nuxt-composables.md
|
|
133
|
-
|
|
134
|
-
## Advanced Patterns
|
|
135
|
-
|
|
136
|
-
### Singleton Composable
|
|
137
|
-
|
|
138
|
-
Share state across all components using the same composable:
|
|
139
|
-
|
|
140
|
-
```ts
|
|
141
|
-
import { createSharedComposable } from '@vueuse/core'
|
|
142
|
-
|
|
143
|
-
function useMapControlsBase() {
|
|
144
|
-
const mapInstance = ref<Map | null>(null)
|
|
145
|
-
const flyTo = (coords: [number, number]) => mapInstance.value?.flyTo(coords)
|
|
146
|
-
return { mapInstance, flyTo }
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
export const useMapControls = createSharedComposable(useMapControlsBase)
|
|
150
|
-
```
|
|
151
|
-
|
|
152
|
-
### Cancellable Fetch with AbortController
|
|
153
|
-
|
|
154
|
-
```ts
|
|
155
|
-
export function useSearch() {
|
|
156
|
-
let abortController: AbortController | null = null
|
|
157
|
-
|
|
158
|
-
watch(query, async (newQuery) => {
|
|
159
|
-
abortController?.abort()
|
|
160
|
-
abortController = new AbortController()
|
|
161
|
-
|
|
162
|
-
try {
|
|
163
|
-
const data = await $fetch('/api/search', {
|
|
164
|
-
query: { q: newQuery },
|
|
165
|
-
signal: abortController.signal,
|
|
166
|
-
})
|
|
167
|
-
}
|
|
168
|
-
catch (e) {
|
|
169
|
-
if (e.name !== 'AbortError')
|
|
170
|
-
throw e
|
|
171
|
-
}
|
|
172
|
-
})
|
|
173
|
-
}
|
|
174
|
-
```
|
|
175
|
-
|
|
176
|
-
### Step-Based State Machine
|
|
177
|
-
|
|
178
|
-
```ts
|
|
179
|
-
export function useSendFlow() {
|
|
180
|
-
const step = ref<'input' | 'confirm' | 'success'>('input')
|
|
181
|
-
const amount = ref('')
|
|
182
|
-
|
|
183
|
-
const next = () => {
|
|
184
|
-
if (step.value === 'input')
|
|
185
|
-
step.value = 'confirm'
|
|
186
|
-
else if (step.value === 'confirm')
|
|
187
|
-
step.value = 'success'
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
return { step, amount, next }
|
|
191
|
-
}
|
|
192
|
-
```
|
|
193
|
-
|
|
194
|
-
### Client-Only Guards
|
|
195
|
-
|
|
196
|
-
```ts
|
|
197
|
-
export function useUserLocation() {
|
|
198
|
-
const location = ref<GeolocationPosition | null>(null)
|
|
199
|
-
|
|
200
|
-
if (import.meta.client) {
|
|
201
|
-
navigator.geolocation.getCurrentPosition(pos => location.value = pos)
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
return { location }
|
|
205
|
-
}
|
|
206
|
-
```
|
|
207
|
-
|
|
208
|
-
### Custom Element Composables (Vue 3.5+)
|
|
209
|
-
|
|
210
|
-
For custom element components, use built-in helpers:
|
|
211
|
-
|
|
212
|
-
```ts
|
|
213
|
-
import { useHost, useShadowRoot } from 'vue'
|
|
214
|
-
|
|
215
|
-
export function useCustomElement() {
|
|
216
|
-
const host = useHost() // Host element reference
|
|
217
|
-
const shadowRoot = useShadowRoot() // Shadow DOM root
|
|
218
|
-
|
|
219
|
-
onMounted(() => {
|
|
220
|
-
console.log('Host:', host)
|
|
221
|
-
console.log('Shadow:', shadowRoot)
|
|
222
|
-
})
|
|
223
|
-
|
|
224
|
-
return { host, shadowRoot }
|
|
225
|
-
}
|
|
226
|
-
```
|
|
227
|
-
|
|
228
|
-
**Available in:**
|
|
229
|
-
|
|
230
|
-
- Components using `<script setup>` in custom elements
|
|
231
|
-
- Access via `this.$host` in Options API
|
|
232
|
-
|
|
233
|
-
### Auto-Save with Debounce
|
|
234
|
-
|
|
235
|
-
```ts
|
|
236
|
-
export function useAutoSave(content: Ref<string>) {
|
|
237
|
-
const hasChanges = ref(false)
|
|
238
|
-
|
|
239
|
-
const save = useDebounceFn(async () => {
|
|
240
|
-
if (!hasChanges.value)
|
|
241
|
-
return
|
|
242
|
-
await $fetch('/api/save', { method: 'POST', body: { content: content.value } })
|
|
243
|
-
hasChanges.value = false
|
|
244
|
-
}, 1000)
|
|
245
|
-
|
|
246
|
-
watch(content, () => {
|
|
247
|
-
hasChanges.value = true
|
|
248
|
-
save()
|
|
249
|
-
})
|
|
250
|
-
|
|
251
|
-
return { hasChanges }
|
|
252
|
-
}
|
|
253
|
-
```
|
|
254
|
-
|
|
255
|
-
### Tagged Logger
|
|
256
|
-
|
|
257
|
-
```ts
|
|
258
|
-
import { consola } from 'consola'
|
|
259
|
-
|
|
260
|
-
export function useSearch() {
|
|
261
|
-
const logger = consola.withTag('search')
|
|
262
|
-
|
|
263
|
-
watch(query, (q) => {
|
|
264
|
-
logger.info('Query changed:', q)
|
|
265
|
-
})
|
|
266
|
-
}
|
|
267
|
-
```
|
|
268
|
-
|
|
269
|
-
## Reactivity Gotchas
|
|
270
|
-
|
|
271
|
-
### Ref Unwrapping in Reactive
|
|
272
|
-
|
|
273
|
-
Refs auto-unwrap in `reactive()` objects but **NOT** in arrays, Maps, or Sets:
|
|
274
|
-
|
|
275
|
-
```ts
|
|
276
|
-
// ✅ Object - auto unwraps
|
|
277
|
-
const state = reactive({ count: ref(0) })
|
|
278
|
-
state.count++ // No .value needed
|
|
279
|
-
|
|
280
|
-
// ❌ Array - NO unwrapping
|
|
281
|
-
const arr = reactive([ref(1)])
|
|
282
|
-
arr[0].value // Need .value!
|
|
283
|
-
|
|
284
|
-
// ❌ Map/Set - NO unwrapping
|
|
285
|
-
const map = reactive(new Map([['key', ref(1)]]))
|
|
286
|
-
map.get('key').value // Need .value!
|
|
287
|
-
```
|
|
288
|
-
|
|
289
|
-
### watchEffect Conditional Tracking
|
|
290
|
-
|
|
291
|
-
Dependencies inside conditional branches are **not tracked** when condition is false:
|
|
292
|
-
|
|
293
|
-
```ts
|
|
294
|
-
// ❌ Wrong - dep not tracked when condition false
|
|
295
|
-
watchEffect(() => {
|
|
296
|
-
if (condition.value) {
|
|
297
|
-
console.log(dep.value) // Only tracked when condition=true
|
|
298
|
-
}
|
|
299
|
-
})
|
|
300
|
-
|
|
301
|
-
// ✅ Correct - use explicit watch for conditional deps
|
|
302
|
-
watch([condition, dep], ([cond, d]) => {
|
|
303
|
-
if (cond) console.log(d)
|
|
304
|
-
})
|
|
305
|
-
```
|
|
306
|
-
|
|
307
|
-
### Cleanup Patterns
|
|
308
|
-
|
|
309
|
-
**For keep-alive components** - use `onDeactivated`:
|
|
310
|
-
|
|
311
|
-
```ts
|
|
312
|
-
export function usePolling() {
|
|
313
|
-
let interval: NodeJS.Timeout
|
|
314
|
-
|
|
315
|
-
onMounted(() => { interval = setInterval(poll, 5000) })
|
|
316
|
-
onUnmounted(() => clearInterval(interval))
|
|
317
|
-
onDeactivated(() => clearInterval(interval)) // Pause when deactivated
|
|
318
|
-
onActivated(() => { interval = setInterval(poll, 5000) }) // Resume
|
|
319
|
-
}
|
|
320
|
-
```
|
|
321
|
-
|
|
322
|
-
**For scope-aware cleanup** - use `tryOnScopeDispose` from VueUse:
|
|
323
|
-
|
|
324
|
-
```ts
|
|
325
|
-
import { tryOnScopeDispose } from '@vueuse/core'
|
|
326
|
-
|
|
327
|
-
export function useEventSource(url: string) {
|
|
328
|
-
const source = new EventSource(url)
|
|
329
|
-
|
|
330
|
-
// Cleans up when effect scope disposes (component unmount, watcher stop)
|
|
331
|
-
tryOnScopeDispose(() => source.close())
|
|
332
|
-
|
|
333
|
-
return { source }
|
|
334
|
-
}
|
|
335
|
-
```
|
|
336
|
-
|
|
337
|
-
## Common Mistakes
|
|
338
|
-
|
|
339
|
-
**Not using `readonly()` for internal state:**
|
|
340
|
-
|
|
341
|
-
```ts
|
|
342
|
-
// ❌ Wrong - exposes mutable ref
|
|
343
|
-
return { count }
|
|
344
|
-
|
|
345
|
-
// ✅ Correct - prevents external mutation
|
|
346
|
-
return { count: readonly(count) }
|
|
347
|
-
```
|
|
348
|
-
|
|
349
|
-
**Missing cleanup:**
|
|
350
|
-
|
|
351
|
-
```ts
|
|
352
|
-
// ❌ Wrong - listener never removed
|
|
353
|
-
onMounted(() => target.addEventListener('click', handler))
|
|
354
|
-
|
|
355
|
-
// ✅ Correct - cleanup on unmount
|
|
356
|
-
onMounted(() => target.addEventListener('click', handler))
|
|
357
|
-
onUnmounted(() => target.removeEventListener('click', handler))
|
|
358
|
-
```
|
|
@@ -1,225 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: Custom Directives
|
|
3
|
-
description: Create reusable directives for low-level DOM manipulation
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Custom Directives
|
|
7
|
-
|
|
8
|
-
Custom directives provide low-level DOM access for reusable behavior.
|
|
9
|
-
|
|
10
|
-
## When to Use
|
|
11
|
-
|
|
12
|
-
Use custom directives when:
|
|
13
|
-
|
|
14
|
-
- You need direct DOM manipulation
|
|
15
|
-
- The behavior can't be achieved with components or composables
|
|
16
|
-
- You need to apply behavior to native elements
|
|
17
|
-
|
|
18
|
-
## Basic Example
|
|
19
|
-
|
|
20
|
-
```vue
|
|
21
|
-
<script setup lang="ts">
|
|
22
|
-
// v-focus directive
|
|
23
|
-
const vFocus = {
|
|
24
|
-
mounted: (el: HTMLElement) => el.focus()
|
|
25
|
-
}
|
|
26
|
-
</script>
|
|
27
|
-
|
|
28
|
-
<template>
|
|
29
|
-
<input v-focus />
|
|
30
|
-
</template>
|
|
31
|
-
```
|
|
32
|
-
|
|
33
|
-
## Directive Hooks
|
|
34
|
-
|
|
35
|
-
```ts
|
|
36
|
-
const myDirective = {
|
|
37
|
-
// Before element attributes/listeners are applied
|
|
38
|
-
created(el, binding, vnode) {},
|
|
39
|
-
|
|
40
|
-
// Before element is inserted into DOM
|
|
41
|
-
beforeMount(el, binding, vnode) {},
|
|
42
|
-
|
|
43
|
-
// After element and children are mounted
|
|
44
|
-
mounted(el, binding, vnode) {},
|
|
45
|
-
|
|
46
|
-
// Before parent component updates
|
|
47
|
-
beforeUpdate(el, binding, vnode, prevVnode) {},
|
|
48
|
-
|
|
49
|
-
// After parent component updates
|
|
50
|
-
updated(el, binding, vnode, prevVnode) {},
|
|
51
|
-
|
|
52
|
-
// Before parent component unmounts
|
|
53
|
-
beforeUnmount(el, binding, vnode) {},
|
|
54
|
-
|
|
55
|
-
// After parent component unmounts
|
|
56
|
-
unmounted(el, binding, vnode) {}
|
|
57
|
-
}
|
|
58
|
-
```
|
|
59
|
-
|
|
60
|
-
## Hook Arguments
|
|
61
|
-
|
|
62
|
-
```ts
|
|
63
|
-
interface DirectiveBinding<T = any> {
|
|
64
|
-
value: T // v-my-dir="value"
|
|
65
|
-
oldValue: T // Previous value (beforeUpdate/updated only)
|
|
66
|
-
arg?: string // v-my-dir:arg
|
|
67
|
-
modifiers: Record<string, boolean> // v-my-dir.foo.bar → { foo: true, bar: true }
|
|
68
|
-
instance: ComponentPublicInstance // Component using the directive
|
|
69
|
-
dir: ObjectDirective // Directive definition object
|
|
70
|
-
}
|
|
71
|
-
```
|
|
72
|
-
|
|
73
|
-
Example usage:
|
|
74
|
-
|
|
75
|
-
```vue-html
|
|
76
|
-
<div v-example:foo.bar="baz">
|
|
77
|
-
```
|
|
78
|
-
|
|
79
|
-
```ts
|
|
80
|
-
// binding object:
|
|
81
|
-
{
|
|
82
|
-
arg: 'foo',
|
|
83
|
-
modifiers: { bar: true },
|
|
84
|
-
value: /* value of baz */,
|
|
85
|
-
oldValue: /* previous value */
|
|
86
|
-
}
|
|
87
|
-
```
|
|
88
|
-
|
|
89
|
-
## Function Shorthand
|
|
90
|
-
|
|
91
|
-
When you only need `mounted` and `updated` with same behavior:
|
|
92
|
-
|
|
93
|
-
```ts
|
|
94
|
-
// Full form
|
|
95
|
-
const vColor = {
|
|
96
|
-
mounted(el, binding) {
|
|
97
|
-
el.style.color = binding.value
|
|
98
|
-
},
|
|
99
|
-
updated(el, binding) {
|
|
100
|
-
el.style.color = binding.value
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
// Shorthand (same behavior)
|
|
105
|
-
const vColor = (el: HTMLElement, binding: DirectiveBinding<string>) => {
|
|
106
|
-
el.style.color = binding.value
|
|
107
|
-
}
|
|
108
|
-
```
|
|
109
|
-
|
|
110
|
-
## Global Registration
|
|
111
|
-
|
|
112
|
-
```ts
|
|
113
|
-
// main.ts
|
|
114
|
-
const app = createApp(App)
|
|
115
|
-
|
|
116
|
-
app.directive('focus', {
|
|
117
|
-
mounted: (el) => el.focus()
|
|
118
|
-
})
|
|
119
|
-
|
|
120
|
-
// Shorthand
|
|
121
|
-
app.directive('color', (el, binding) => {
|
|
122
|
-
el.style.color = binding.value
|
|
123
|
-
})
|
|
124
|
-
```
|
|
125
|
-
|
|
126
|
-
## Object Literals
|
|
127
|
-
|
|
128
|
-
Pass multiple values:
|
|
129
|
-
|
|
130
|
-
```vue-html
|
|
131
|
-
<div v-demo="{ color: 'white', text: 'hello' }">
|
|
132
|
-
```
|
|
133
|
-
|
|
134
|
-
```ts
|
|
135
|
-
const vDemo = (el: HTMLElement, binding: DirectiveBinding<{ color: string; text: string }>) => {
|
|
136
|
-
console.log(binding.value.color) // 'white'
|
|
137
|
-
console.log(binding.value.text) // 'hello'
|
|
138
|
-
}
|
|
139
|
-
```
|
|
140
|
-
|
|
141
|
-
## Dynamic Arguments
|
|
142
|
-
|
|
143
|
-
```vue-html
|
|
144
|
-
<div v-my-directive:[dynamicArg]="value">
|
|
145
|
-
```
|
|
146
|
-
|
|
147
|
-
## Practical Examples
|
|
148
|
-
|
|
149
|
-
### v-click-outside
|
|
150
|
-
|
|
151
|
-
```ts
|
|
152
|
-
const vClickOutside = {
|
|
153
|
-
mounted(el: HTMLElement, binding: DirectiveBinding<() => void>) {
|
|
154
|
-
el._clickOutside = (event: MouseEvent) => {
|
|
155
|
-
if (!el.contains(event.target as Node)) {
|
|
156
|
-
binding.value()
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
document.addEventListener('click', el._clickOutside)
|
|
160
|
-
},
|
|
161
|
-
unmounted(el: HTMLElement) {
|
|
162
|
-
document.removeEventListener('click', el._clickOutside)
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
```
|
|
166
|
-
|
|
167
|
-
### v-tooltip
|
|
168
|
-
|
|
169
|
-
```ts
|
|
170
|
-
const vTooltip = {
|
|
171
|
-
mounted(el: HTMLElement, binding: DirectiveBinding<string>) {
|
|
172
|
-
el.setAttribute('title', binding.value)
|
|
173
|
-
},
|
|
174
|
-
updated(el: HTMLElement, binding: DirectiveBinding<string>) {
|
|
175
|
-
el.setAttribute('title', binding.value)
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
```
|
|
179
|
-
|
|
180
|
-
### v-permission
|
|
181
|
-
|
|
182
|
-
```ts
|
|
183
|
-
const vPermission = {
|
|
184
|
-
mounted(el: HTMLElement, binding: DirectiveBinding<string>) {
|
|
185
|
-
if (!hasPermission(binding.value)) {
|
|
186
|
-
el.parentNode?.removeChild(el)
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
```
|
|
191
|
-
|
|
192
|
-
## TypeScript: Global Directives
|
|
193
|
-
|
|
194
|
-
```ts
|
|
195
|
-
// directives/highlight.ts
|
|
196
|
-
import type { Directive } from 'vue'
|
|
197
|
-
|
|
198
|
-
export type HighlightDirective = Directive<HTMLElement, string>
|
|
199
|
-
|
|
200
|
-
declare module 'vue' {
|
|
201
|
-
export interface ComponentCustomProperties {
|
|
202
|
-
vHighlight: HighlightDirective
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
export default {
|
|
207
|
-
mounted: (el, binding) => {
|
|
208
|
-
el.style.backgroundColor = binding.value
|
|
209
|
-
}
|
|
210
|
-
} satisfies HighlightDirective
|
|
211
|
-
```
|
|
212
|
-
|
|
213
|
-
## Usage on Components
|
|
214
|
-
|
|
215
|
-
⚠️ **Not recommended** - directives apply to root element, which can be unpredictable with multi-root components.
|
|
216
|
-
|
|
217
|
-
```vue-html
|
|
218
|
-
<!-- Applies to MyComponent's root element -->
|
|
219
|
-
<MyComponent v-my-directive />
|
|
220
|
-
```
|
|
221
|
-
|
|
222
|
-
<!--
|
|
223
|
-
Source references:
|
|
224
|
-
- https://vuejs.org/guide/reusability/custom-directives.html
|
|
225
|
-
-->
|