# v2 to v3 Migration Guide

Complete step-by-step guide for upgrading your application from Grimoire v2 to v3.

## 📋 Migration Overview

v3 is a major modernization release with **one primary breaking change** for consumers: **StyledComponent ref access pattern**.

**Estimated Migration Time**: 30 minutes - 2 hours (depending on project size)

**Difficulty**: 🟡 Medium (mostly find-and-replace patterns)

---

## ⚠️ CRITICAL: Search Your ENTIRE Codebase

**DO NOT migrate only one component or file at a time!**

This migration guide requires you to:

1. ✅ **Search your ENTIRE codebase** for affected patterns
2. ✅ **Update ALL occurrences** at once
3. ✅ **Test your ENTIRE application** after migration
4. ✅ **Update ALL test files** (especially for GButton class assertions)

**Why this matters:**
- `@refMounted` may be used in dozens of files across your project
- GButton class changes may break unit tests you forgot about
- Partial migrations will leave your codebase in a broken state
- Some changes affect imports, tests, and components simultaneously

**Use these commands to find ALL affected files:**

```bash
# Find all @refMounted usage
grep -r "@refMounted" src/ tests/

# Find all GButton usage (for test updates)
grep -r "GButton" src/ tests/

# Find all plugin imports
grep -r "grimoire/plugin" src/

# Count total occurrences
echo "Total @refMounted usage: $(grep -r "@refMounted" src/ | wc -l)"
```

**Expected scope**: If you have a medium-sized project, expect to update 10-50 files minimum.

---

## ✨ What's New in v3

### TypeScript Support for Component Refs

v3 introduces **exported `*Exposed` types** for all components that use `defineExpose`, giving you full TypeScript autocomplete and type safety when accessing component methods via refs.

```typescript
import { ref } from 'vue';
import { type GDropdownExposed } from '@twentyfourg/grimoire';

const dropdownRef = ref<GDropdownExposed>();

// TypeScript now knows about ALL methods! ✅
dropdownRef.value?.open();    // VueMultiselect method
dropdownRef.value?.toggle();  // Custom GDropdown method
```

**Available for all components:**
- `GDropdownExposed`, `GDatepickerExposed`, `GInputExposed`
- `GFormExposed`, `GFormFieldExposed`, `GFormFieldInputExposed`
- `GModalExposed`, `GTableExposed`, `GTabsExposed`
- And more! See [Issue 5](#issue-5-typescript-errors-on-refs) for complete list.

### Other Improvements

- ✅ Simplified ref access pattern (no more `@refMounted`)
- ✅ Better styled component composable (`useStyledComponent`)
- ✅ Full TypeScript inference for component props and refs
- ✅ Cleaner code with direct template refs

---

## 🎯 Breaking Changes Summary

| Change                                  | Impact                                           | Difficulty |
| --------------------------------------- | ------------------------------------------------ | ---------- |
| `@refMounted` → Direct refs             | 🔴 **HIGH** - Affects all styled component usage | Easy       |
| Vue version bump (^3.3.0)               | 🟡 Medium - May require updating Vue             | Easy       |
| Plugin exports (`.ts` instead of `.js`) | 🟢 Low - Only affects plugin registration        | Easy       |
| Iconify build tools                     | 🟢 Low - Only if building custom icon bundles    | Medium     |

See [`v2-to-v3-breaking-changes.md`](./v2-to-v3-breaking-changes.md) for comprehensive details.

---

## ✅ Pre-Migration Checklist

Before starting:

- [ ] **Backup your project** - Create a new git branch: `git checkout -b migrate-grimoire-v3`
- [ ] **Check Vue version** - Ensure you're on Vue ^3.3.0 or higher
- [ ] **⚠️ SCAN ENTIRE CODEBASE** - Run all grep commands above to find ALL affected files
- [ ] **Count occurrences** - Know how many files you need to update (not just 1 or 2!)
- [ ] **Check test files** - Include `tests/` or `__tests__/` directories in your search
- [ ] **Read breaking changes** - Review [`v2-to-v3-breaking-changes.md`](./v2-to-v3-breaking-changes.md)
- [ ] **Set aside time** - Block 1-2 hours to complete the FULL migration (not partial)

---

## 📦 Step 1: Update Package

```bash
# Using pnpm
pnpm update @twentyfourg/grimoire@^3.0.0

# Using npm
npm install @twentyfourg/grimoire@^3.0.0

# Using yarn
yarn upgrade @twentyfourg/grimoire@^3.0.0
```

**Verify installation:**

```bash
npm list @twentyfourg/grimoire
# Should show v3.x.x
```

---

## 🔧 Step 2: Update Vue Version (if needed)

If you're on Vue < 3.3.0:

```bash
pnpm update vue@^3.3.0
# or
npm install vue@^3.3.0
```

**Check your `package.json`:**

```json
{
  "dependencies": {
    "vue": "^3.3.0"
  }
}
```

---

## 🎯 Step 3: Migrate `@refMounted` to Direct Refs

This is the **main breaking change** for most projects.

### ⚠️ Find ALL Affected Components (Not Just One!)

**IMPORTANT**: You MUST migrate ALL `@refMounted` usage at once. Don't stop after finding one file!

```bash
# Find ALL @refMounted usage in your ENTIRE project
grep -r "@refMounted" src/ tests/ --include="*.vue" --include="*.js" --include="*.ts"

# Get a count of total occurrences
grep -r "@refMounted" src/ tests/ | wc -l

# Example output (YOUR project will differ):
# src/components/UserProfile.vue:    <StyledDropdown @refMounted="handleDropdownRef" />
# src/views/Settings.vue:    <StyledInput @refMounted="inputRef = $event" />
# src/components/Dashboard.vue:    <StyledModal @refMounted="modalRef = $event" />
# src/forms/ContactForm.vue:    <StyledForm @refMounted="formRef = $event" />
# tests/UserProfile.spec.js:    await wrapper.find('StyledDropdown').trigger('refMounted', ref);
# ... (expect 10-50+ matches in a typical project)
```

**Create a migration checklist:**

```bash
# Save all affected files to a list
grep -rl "@refMounted" src/ tests/ > migration-files.txt

# Review the list
cat migration-files.txt

# Check them off as you update each one!
```

### Migration Pattern

**OLD (v2) - Using Options API:**

```vue
<template>
  <div>
    <StyledDropdown @refMounted="handleDropdownRef" :options="options" />
    <button @click="openDropdown">Open</button>
  </div>
</template>

<script>
export default {
  data() {
    return {
      dropdownRef: null,
      options: ['Option 1', 'Option 2', 'Option 3'],
    };
  },
  methods: {
    handleDropdownRef(ref) {
      this.dropdownRef = ref;
    },
    openDropdown() {
      this.dropdownRef.open();
    },
  },
};
</script>
```

**NEW (v3) - Using Composition API:**

```vue
<template>
  <div>
    <StyledDropdown ref="dropdownRef" :options="options" />
    <button @click="openDropdown">Open</button>
  </div>
</template>

<script setup>
import { ref } from 'vue';

const dropdownRef = ref(null);
const options = ref(['Option 1', 'Option 2', 'Option 3']);

function openDropdown() {
  dropdownRef.value?.open();
}
</script>
```

**NEW (v3) - Staying with Options API:**

```vue
<template>
  <div>
    <StyledDropdown ref="dropdownRef" :options="options" />
    <button @click="openDropdown">Open</button>
  </div>
</template>

<script>
export default {
  data() {
    return {
      options: ['Option 1', 'Option 2', 'Option 3'],
    };
  },
  methods: {
    openDropdown() {
      this.$refs.dropdownRef?.open();
    },
  },
};
</script>
```

### Step-by-Step Conversion

For each component using `@refMounted`:

1. **Remove the `@refMounted` event**:

```diff
- <StyledDropdown @refMounted="handleRef" />
+ <StyledDropdown ref="dropdownRef" />
```

2. **Remove the handler method** (if using Options API):

```diff
- methods: {
-   handleRef(ref) {
-     this.dropdownRef = ref;
-   },
- },
```

3. **Update method calls**:

**Composition API:**

```diff
- dropdownRef.open();
+ dropdownRef.value?.open();
```

**Options API:**

```diff
- this.dropdownRef.open();
+ this.$refs.dropdownRef?.open();
```

4. **Handle `onMounted` if needed**:

If you were doing something in the handler beyond just storing the ref:

**OLD:**

```javascript
methods: {
  handleRef(ref) {
    this.dropdownRef = ref;
    // Initialize something
    ref.setInitialValue('default');
  }
}
```

**NEW (Composition API):**

```javascript
import { onMounted } from 'vue';

onMounted(() => {
  dropdownRef.value?.setInitialValue('default');
});
```

**NEW (Options API):**

```javascript
mounted() {
  this.$refs.dropdownRef?.setInitialValue('default');
}
```

---

## 🔌 Step 4: Update Plugin Registration (if applicable)

If you're using the Grimoire plugin for Iconify:

**OLD (v2):**

```javascript
import GrimoirePlugin from '@twentyfourg/grimoire/plugin';
```

**NEW (v3):**

```javascript
import { GrimoirePlugin } from '@twentyfourg/grimoire/plugin';
```

**Note**: The plugin is only for registering Iconify icon collections. Components are auto-imported or imported directly.

---

## 🎨 Step 5: Update Icon Build Tools (if using custom icons)

If you're building custom icon bundles from SVG sources:

**OLD (v2):**

```javascript
// Icon bundles were pre-built and imported directly
import '@twentyfourg/grimoire/icons/bundle';
```

**NEW (v3):**

```javascript
// Use build tools to create bundles from your SVGs
// See ai-docs/iconify-setup.md for detailed setup
```

If you're NOT building custom icons, you can skip this step.

---

## 🧪 Step 6: Test Your Application

### Run Your Test Suite

```bash
# Run unit tests
npm test

# Run E2E tests
npm run test:e2e
```

### Manual Testing Checklist

For each StyledComponent you migrated:

- [ ] Component renders correctly
- [ ] Props work as expected
- [ ] Events fire correctly
- [ ] Ref methods are accessible
- [ ] No console errors or warnings

### Common Things to Check

1. **Dropdown components**: Can you open/close them programmatically?
2. **Input components**: Can you focus/blur them?
3. **Form components**: Can you access validation methods?
4. **Modal components**: Can you open/close them?
5. **⚠️ GButton tests**: Check for duplicate class assertions (see below)

### ⚠️ IMPORTANT: GButton Test Failures

**GButton now properly sets `inheritAttrs: false`**, which means classes are **no longer duplicated** on the wrapper element.

**This WILL break tests that relied on duplicate classes!**

```javascript
// ❌ Tests that will FAIL in v3:
const wrapper = mount(GButton, { attrs: { class: 'my-button' } });
expect(wrapper.classes()).toContain('my-button'); // ❌ Wrapper doesn't have class anymore
expect(wrapper.find('.my-button').exists()).toBe(true); // ❌ No wrapper with this class

// ✅ Tests that WORK in v3:
const wrapper = mount(GButton, { attrs: { class: 'my-button' } });
expect(wrapper.find('button').classes()).toContain('my-button'); // ✅ Button has class
expect(wrapper.find('button.my-button').exists()).toBe(true); // ✅ Button selector works
```

**Action Required:**
```bash
# Find all GButton tests
grep -r "GButton" tests/ __tests__/ src/**/*.spec.* src/**/*.test.*

# Look for patterns like:
# - wrapper.classes()
# - wrapper.find('.class-name')
# - expect(wrapper.attributes('class'))

# Update them to target the <button> element:
# - wrapper.find('button').classes()
# - wrapper.find('button.class-name')
# - expect(wrapper.find('button').attributes('class'))
```

---

## 🔍 Step 7: Search for Deprecation Warnings

Run your app in development mode and check the console:

```bash
npm run dev
```

Look for warnings like:

- ⚠️ `@refMounted is deprecated in v3`
- ⚠️ Missing icon collections
- ⚠️ Old prop usage

---

## 📊 Migration Script

Automate finding affected files:

```bash
#!/bin/bash
# save as migrate-grimoire.sh

echo "🔍 Searching for @refMounted usage..."
echo ""

# Find all files with @refMounted
files=$(grep -rl "@refMounted" src/)

if [ -z "$files" ]; then
  echo "✅ No @refMounted usage found!"
  exit 0
fi

echo "📋 Files to migrate:"
echo "$files" | nl
echo ""
echo "Total: $(echo "$files" | wc -l) files"
echo ""
echo "💡 For each file:"
echo "  1. Replace @refMounted=\"handler\" with ref=\"refName\""
echo "  2. Remove handler method"
echo "  3. Update method calls to use .value"
```

Run it:

```bash
chmod +x migrate-grimoire.sh
./migrate-grimoire.sh
```

---

## 🎨 Step 8: Upgrade Custom StyledComponents (Optional)

If you created custom StyledComponents in your project (not just using Grimoire's), you should upgrade them to use the new `useStyledComponent` composable.

> **💡 Key Pattern**: Put static defaults in `defineProps`. Only use `useStyledComponent` config for conditional logic.

### Check if You Need This

Search for custom StyledComponents:

```bash
# Look for components wrapping Grimoire components
grep -r "from '@twentyfourg/grimoire'" src/components/styled/
```

If you have files like `StyledButton.vue`, `StyledInput.vue`, etc., you should upgrade them.

### Old Pattern (v2)

Your v2 StyledComponents might have used various patterns:

**Pattern 1: Manual computedProps**

```vue
<template>
  <GButton v-bind="computedProps" class="styled-button">
    <slot />
  </GButton>
</template>

<script>
import { GButton } from '@twentyfourg/grimoire';

export default {
  components: { GButton },
  props: {
    variant: {
      type: String,
      default: 'primary',
    },
  },
  computed: {
    computedProps() {
      return {
        ...this.$props,
        rounded: true,
      };
    },
  },
};
</script>
```

**Pattern 2: Using @refMounted**

```vue
<template>
  <GDropdown @refMounted="$emit('refMounted', $event)" v-bind="$props">
    <slot />
  </GDropdown>
</template>

<script>
export default {
  emits: ['refMounted'],
};
</script>
```

### New Pattern (v3)

Upgrade to use `useStyledComponent`:

```vue
<template>
  <GButton ref="buttonRef" v-bind="mergedProps" class="styled-button">
    <slot />
  </GButton>
</template>

<script setup lang="ts">
import { ref } from 'vue';
import { GButton, type GButtonExposed } from '@twentyfourg/grimoire';
import { useStyledComponent } from '@twentyfourg/grimoire/composables';

const props = defineProps({
  ...GButton.props,

  // Override defaults directly in defineProps
  rounded: {
    type: Boolean,
    default: true, // Your project default
  },

  variant: {
    type: String,
    default: 'primary',
  },
});

const buttonRef = ref<GButtonExposed>();

// Simple case - no config needed!
// ⚠️ Type parameter is REQUIRED for TypeScript support
const { mergedProps, expose } = useStyledComponent<GButtonExposed>(buttonRef, props);

defineExpose(expose);

// Export the type for consumers of your StyledComponent
export type StyledButtonExposed = GButtonExposed;

defineOptions({
  inheritAttrs: false,
});
</script>
```

### ⚠️ Important: Type Parameter Required

When using `useStyledComponent`, you **MUST** provide the type parameter for proper TypeScript support:

```typescript
// ❌ WRONG - TypeScript won't know the exposed methods
const { mergedProps, expose } = useStyledComponent(dropdownRef, props);

// ✅ CORRECT - Full TypeScript autocomplete support
const { mergedProps, expose } = useStyledComponent<GDropdownExposed>(dropdownRef, props);
```

Without the type parameter, TypeScript will treat `expose` as `unknown`, and users won't get autocomplete for your component's methods.

### Migration Steps for Each StyledComponent

#### 1. Convert to Composition API

**Old:**

```vue
<script>
export default {
  props: {...},
  computed: {...},
};
</script>
```

**New:**

```vue
<script setup>
import { ref } from 'vue';
</script>
```

#### 2. Import useStyledComponent

```javascript
import { useStyledComponent } from '@twentyfourg/grimoire/composables';
```

#### 3. Update Props Definition

**Old:**

```javascript
export default {
  props: {
    variant: { type: String, default: 'primary' },
  },
};
```

**New:**

```javascript
const props = defineProps({
  ...GButton.props, // Inherit all base props

  // Override defaults by redefining the prop
  rounded: {
    type: Boolean,
    default: true, // Your project default
  },

  variant: {
    type: String,
    default: 'primary',
  },
});
```

#### 4. Create Ref and Use Composable

```typescript
import { type GButtonExposed } from '@twentyfourg/grimoire';

const buttonRef = ref<GButtonExposed>();

// Simple case - no config needed
// ⚠️ Type parameter is REQUIRED for TypeScript support
const { mergedProps, expose } = useStyledComponent<GButtonExposed>(buttonRef, props);

// Only use config for conditional logic (see "Advanced" section below)
```

#### 5. Update Template

**Old:**

```vue
<GButton v-bind="computedProps"></GButton>
```

**New:**

```vue
<GButton ref="buttonRef" v-bind="mergedProps"></GButton>
```

#### 6. Expose API

```javascript
defineExpose(expose);
```

#### 7. Add inheritAttrs (if needed)

```javascript
defineOptions({
  inheritAttrs: false,
});
```

### Advanced: Conditional Logic with computeProps

**Important**: Static defaults go in `defineProps`. Only use `computeProps` for **conditional logic** based on other props.

If you had conditional logic in your computed props:

**Old:**

```javascript
computed: {
  computedProps() {
    const props = { ...this.$props };

    // Conditional logic
    if (this.mode === 'tags') {
      props.hideSelected = false;
      props.closeOnSelect = false;
    }

    return props;
  },
},
```

**New:**

```typescript
import { ref } from 'vue';
import { GDropdown, type GDropdownExposed } from '@twentyfourg/grimoire';
import { useStyledComponent } from '@twentyfourg/grimoire/composables';

// Static defaults in defineProps
const props = defineProps({
  ...GDropdown.props,
  canClear: {
    type: Boolean,
    default: false, // ← Static default
  },
});

const dropdownRef = ref<GDropdownExposed>();

// Conditional logic in computeProps
// ⚠️ Type parameter is REQUIRED for TypeScript support
const { mergedProps, expose } = useStyledComponent<GDropdownExposed>(dropdownRef, props, {
  computeProps(merged, attrs) {
    // ← Only conditional logic here
    const mode = attrs.mode || merged.mode;
    if (mode === 'tags' || mode === 'multiple') {
      merged.hideSelected = merged.hideSelected ?? false;
      merged.closeOnSelect = merged.closeOnSelect ?? false;
    }
    return merged;
  },
});
```

### Complete Example: StyledDropdown Migration

**Before (v2):**

```vue
<template>
  <GDropdown @refMounted="handleRef" v-bind="computedProps" class="styled-dropdown">
    <slot />
  </GDropdown>
</template>

<script>
import { GDropdown } from '@twentyfourg/grimoire';

export default {
  components: { GDropdown },
  emits: ['refMounted'],
  props: {
    ...GDropdown.props,
    variant: String,
  },
  computed: {
    computedProps() {
      const props = { ...this.$props, canClear: false };

      if (this.mode === 'tags') {
        props.hideSelected = false;
      }

      return props;
    },
  },
  methods: {
    handleRef(ref) {
      this.$emit('refMounted', ref);
    },
  },
};
</script>
```

**After (v3):**

```vue
<template>
  <GDropdown ref="dropdownRef" v-bind="mergedProps" class="styled-dropdown">
    <slot />
  </GDropdown>
</template>

<script setup lang="ts">
import { ref } from 'vue';
import { GDropdown, type GDropdownExposed } from '@twentyfourg/grimoire';
import { useStyledComponent } from '@twentyfourg/grimoire/composables';

const props = defineProps({
  ...GDropdown.props,

  // Static defaults in defineProps
  canClear: {
    type: Boolean,
    default: false,
  },

  variant: String,
});

const dropdownRef = ref<GDropdownExposed>();

// Only conditional logic in computeProps
// ⚠️ Type parameter is REQUIRED for TypeScript support
const { mergedProps, expose } = useStyledComponent<GDropdownExposed>(dropdownRef, props, {
  computeProps(merged, attrs) {
    const mode = attrs.mode || merged.mode;
    if (mode === 'tags' || mode === 'multiple') {
      merged.hideSelected = merged.hideSelected ?? false;
    }
    return merged;
  },
});

defineExpose(expose);

// Export the type for consumers of your StyledComponent
export type StyledDropdownExposed = GDropdownExposed;

defineOptions({
  inheritAttrs: false,
});
</script>
```

### Benefits of Upgrading

1. ✅ **Automatic method forwarding** - Users can call `styledRef.value.open()` directly
2. ✅ **No @refMounted needed** - Cleaner API for users of your StyledComponents
3. ✅ **Better TypeScript support** - Full type inference
4. ✅ **Consistent pattern** - Same pattern across all styled components
5. ✅ **Less boilerplate** - No manual prop merging

### Migration Checklist for Each StyledComponent

- [ ] Convert `<script>` to `<script setup lang="ts">`
- [ ] Import `useStyledComponent` composable
- [ ] Import the `*Exposed` type for your base component
- [ ] Create typed ref: `const componentRef = ref<GComponentExposed>()`
- [ ] Spread base component props: `...GComponent.props`
- [ ] **Override defaults in `defineProps`** (not in useStyledComponent config)
- [ ] Call `useStyledComponent<GComponentExposed>(componentRef, props)` - **type parameter required!**
- [ ] OR add `computeProps` config if you have conditional logic
- [ ] Update template: `ref="componentRef"` + `v-bind="mergedProps"`
- [ ] Call `defineExpose(expose)`
- [ ] Remove `@refMounted` emit logic
- [ ] Test that all base methods are accessible with TypeScript autocomplete

### Skip This Step If...

You can skip upgrading custom StyledComponents if:

- You don't have any custom StyledComponents
- You're only using Grimoire's pre-built styled theme
- You plan to rewrite them from scratch later

**Note**: Your v2 StyledComponents will still work, but users of those components won't benefit from the improved v3 ref access pattern until you upgrade them.

---

## ✅ Post-Migration Checklist

After completing migration:

- [ ] **All `@refMounted` events removed** - Verify with: `grep -r "@refMounted" src/ tests/` (should return 0 results)
- [ ] **All ref accesses updated** - No compilation errors
- [ ] **All GButton tests updated** - Check for wrapper class assertions
- [ ] **Plugin imports updated** (if used)
- [ ] **All tests pass** - Run full test suite, not just one test file
- [ ] **App runs without console errors** - Check browser console
- [ ] **Manual testing completed** - Test ALL components that used refs, not just one
- [ ] **Documentation updated** (if you have internal docs)
- [ ] **Code reviewed** (if working in a team)
- [ ] **✅ Double-check**: Run all grep commands from Step 3 again to confirm 0 matches

---

## 🐛 Common Issues

### Issue 1: "Cannot read property 'open' of null"

**Cause**: Trying to access ref before component is mounted.

**Fix**:

```javascript
// ❌ Wrong - ref not ready yet
const dropdownRef = ref(null);
dropdownRef.value.open(); // Error!

// ✅ Correct - wait for mount
import { onMounted } from 'vue';

onMounted(() => {
  dropdownRef.value?.open(); // Safe
});
```

### Issue 2: "Cannot read property 'open' of undefined"

**Cause**: Forgot to add `.value` in Composition API.

**Fix**:

```javascript
// ❌ Wrong
dropdownRef.open();

// ✅ Correct
dropdownRef.value.open();
```

### Issue 3: "@refMounted still emitting warnings"

**Cause**: Styled component not updated yet (if you created custom styled components).

**Fix**: Update your custom styled components to use v3 patterns. See [`../../STYLED_COMPONENT_MIGRATION_GUIDE.md`](../../STYLED_COMPONENT_MIGRATION_GUIDE.md)

### Issue 4: "Plugin is not a function"

**Cause**: Plugin import syntax changed.

**Fix**:

```diff
- import GrimoirePlugin from '@twentyfourg/grimoire/plugin';
+ import { GrimoirePlugin } from '@twentyfourg/grimoire/plugin';
```

### Issue 5: GButton tests failing with "expected class not found"

**Cause**: GButton now properly sets `inheritAttrs: false` to prevent duplicate classes. Tests checking for classes on the wrapper element will fail.

**Fix**: Update tests to check the `<button>` element instead of the wrapper:

```javascript
// ❌ OLD (v2) - Checking wrapper
const wrapper = mount(GButton, { attrs: { class: 'my-button' } });
expect(wrapper.classes()).toContain('my-button');
expect(wrapper.find('.my-button').exists()).toBe(true);

// ✅ NEW (v3) - Checking button element
const wrapper = mount(GButton, { attrs: { class: 'my-button' } });
expect(wrapper.find('button').classes()).toContain('my-button');
expect(wrapper.find('button.my-button').exists()).toBe(true);
```

**Find affected tests:**
```bash
grep -r "GButton" tests/ __tests__/ | grep -E "(\.classes\(\)|\.find\('\.)"
```

---

### Issue 6: TypeScript errors on refs

**Cause**: v3 now provides proper exported types for component refs, giving you full TypeScript support and autocomplete for all component methods.

**Fix**: Import and use the specific `*Exposed` types:

```typescript
// ✅ BEST - Use specific exposed types (v3)
import { ref } from 'vue';
import {
  type GDropdownExposed,
  type GInputExposed,
  type GFormExposed
} from '@twentyfourg/grimoire';

const dropdownRef = ref<GDropdownExposed>();
const inputRef = ref<GInputExposed>();
const formRef = ref<GFormExposed>();

// Now TypeScript knows ALL methods!
dropdownRef.value?.open();      // ✅ VueMultiselect method
dropdownRef.value?.toggle();    // ✅ Custom GDropdown method
inputRef.value?.focus();        // ✅ Custom GInput method
formRef.value?.validateForm();  // ✅ GForm method
```

**OLD approach (still works but less type-safe):**

```typescript
// ⚠️ FALLBACK - Generic component type (less ideal)
import type { ComponentPublicInstance } from 'vue';

const dropdownRef = ref<ComponentPublicInstance | null>(null);
// TypeScript won't know about open(), toggle(), etc.
```

**Available Exposed Types:**
- `GDropdownExposed` - Includes all VueMultiselect methods + custom props
- `GDatepickerExposed` - Includes all VueDatePicker methods + custom props
- `GInputExposed` - Input methods (focus, blur, clear, select, etc.)
- `GFormExposed` - Form methods (validateForm, submit, clearForm, etc.)
- `GFormFieldExposed` - Field methods and validation state
- `GFormFieldInputExposed` - Field input methods and validation
- `GModalExposed` - Modal methods (open, close, toggle)
- `GTableExposed` - Table state and methods
- `GTabsExposed` - Tab navigation methods
- `GIconExposed`, `GAvatarExposed`, `GThumbnailExposed`
- `GCollapsibleExposed`, `GPaginationExposed`

See [`typescript-usage.md`](./typescript-usage.md) for detailed TypeScript patterns.

See [`troubleshooting.md`](./troubleshooting.md) for more issues.

---

## 🎯 Example: Complete Migration

### Before (v2)

```vue
<template>
  <div class="user-settings">
    <StyledInput @refMounted="nameInputRef = $event" v-model="userName" label="Name" />

    <StyledDropdown @refMounted="handleDropdownRef" v-model="userRole" :options="roles" label="Role" />

    <StyledButton @click="saveSettings">Save</StyledButton>
  </div>
</template>

<script>
export default {
  data() {
    return {
      userName: 'John Doe',
      userRole: 'admin',
      roles: ['admin', 'user', 'guest'],
      nameInputRef: null,
      dropdownRef: null,
    };
  },
  methods: {
    handleDropdownRef(ref) {
      this.dropdownRef = ref;
    },
    saveSettings() {
      // Validate
      if (!this.userName) {
        this.nameInputRef.focus();
        return;
      }
      // Save logic...
      console.log('Saved!');
    },
    resetForm() {
      this.dropdownRef.clear();
      this.nameInputRef.clear();
    },
  },
};
</script>
```

### After (v3 - Composition API)

```vue
<template>
  <div class="user-settings">
    <StyledInput ref="nameInputRef" v-model="userName" label="Name" />

    <StyledDropdown ref="dropdownRef" v-model="userRole" :options="roles" label="Role" />

    <StyledButton @click="saveSettings">Save</StyledButton>
  </div>
</template>

<script setup lang="ts">
import { ref } from 'vue';
import type { GInputExposed, GDropdownExposed } from '@twentyfourg/grimoire';

const userName = ref('John Doe');
const userRole = ref('admin');
const roles = ref(['admin', 'user', 'guest']);

// ✅ Properly typed refs with full autocomplete
const nameInputRef = ref<GInputExposed>();
const dropdownRef = ref<GDropdownExposed>();

function saveSettings() {
  // Validate
  if (!userName.value) {
    nameInputRef.value?.focus(); // ✅ TypeScript knows this method exists
    return;
  }
  // Save logic...
  console.log('Saved!');
}

function resetForm() {
  dropdownRef.value?.clear(); // ✅ TypeScript knows this method exists
  nameInputRef.value?.clear(); // ✅ TypeScript knows this method exists
}
</script>
```

### After (v3 - Options API)

```vue
<template>
  <div class="user-settings">
    <StyledInput ref="nameInputRef" v-model="userName" label="Name" />

    <StyledDropdown ref="dropdownRef" v-model="userRole" :options="roles" label="Role" />

    <StyledButton @click="saveSettings">Save</StyledButton>
  </div>
</template>

<script>
export default {
  data() {
    return {
      userName: 'John Doe',
      userRole: 'admin',
      roles: ['admin', 'user', 'guest'],
    };
  },
  methods: {
    saveSettings() {
      // Validate
      if (!this.userName) {
        this.$refs.nameInputRef?.focus();
        return;
      }
      // Save logic...
      console.log('Saved!');
    },
    resetForm() {
      this.$refs.dropdownRef?.clear();
      this.$refs.nameInputRef?.clear();
    },
  },
};
</script>
```

---

## ✅ Final Verification: Did You Really Migrate Everything?

Before you consider the migration complete, **verify you migrated your ENTIRE codebase**:

```bash
# ⚠️ CRITICAL: These commands should return 0 results
echo "Checking for remaining @refMounted usage..."
grep -r "@refMounted" src/ tests/ --include="*.vue" --include="*.js" --include="*.ts"

# If you see ANY results, you're NOT done! Keep migrating.
# Expected output: (no results)

echo ""
echo "Checking for old plugin imports..."
grep -r "from '@twentyfourg/grimoire/plugin'" src/ | grep -v "{ GrimoirePlugin }"

# Expected output: (no results)
```

**If you see results:**
- ❌ **STOP** - You have NOT completed the migration
- 🔙 **Go back** to the file(s) shown and migrate them
- 🔁 **Repeat** this verification until you get 0 results

**Only proceed when ALL checks return 0 results!**

---

## 📚 Next Steps

After **complete** migration:

1. **Read component usage guide**: [`component-usage.md`](./component-usage.md)
2. **Learn TypeScript patterns**: [`typescript-usage.md`](./typescript-usage.md)
3. **Explore new features**: Check the main [`README.md`](../../README.md)
4. **Update internal docs**: Document the migration for your team
5. **Share lessons learned**: Help your team understand what changed

---

## 🆘 Need Help?

- **Troubleshooting**: See [`troubleshooting.md`](./troubleshooting.md)
- **GitHub Issues**: [Report a bug](https://github.com/twentyfourg/grimoire/issues)
- **Breaking Changes**: See [`v2-to-v3-breaking-changes.md`](./v2-to-v3-breaking-changes.md)

---

**Migration completed?** 🎉 Welcome to Grimoire v3!
