# @smooth-scrollbar-contrib/vue-wrapper

## Features
- 💚 Works with Vue **3.2**, **2.7**, **2.6**
- 🧱 Component
- ⚗️ Composable
- 📜 Directive **(no SSR)**
- 🍃 Tree shakeable

## Installation

Since `smooth-scrollbar` is an external dependency you have to install it individually

> **Warning**: This package uses composition-api `setup` syntax, `script setup` is recommended

Vue **3.2**, **2.7**, Nuxt **3** and Nuxt Bridge **2.16+**
```bash
npm i smooth-scrollbar @smooth-scrollbar-contrib/vue-wrapper
```

<details>
<summary>Vue 2.6</summary>

```bash
npm i @vue/composition-api smooth-scrollbar @smooth-scrollbar-contrib/vue-wrapper

# bring `<script setup>` to Vue 2.
npm i -D unplugin-vue2-script-setup

# if you using Volar
npm i -D @vue/runtime-dom
```

</details>

<details>
<summary>Nuxt ≤ 2.15.6 and Vue 2.6</summary>

Older version of Nuxt that uses Vue 2.6 

```bash
npm i smooth-scrollbar @smooth-scrollbar-contrib/vue-wrapper

# automatically configured using unplugin-vue2-script-setup
npm i -D @nuxtjs/composition-api

# if you using Volar
npm i -D @vue/runtime-dom
```

</details>

## Usage

### Component
```vue
<template>
  <Scrollbar ref="scrollbarRef" as="div" :scrollbar-options="scrollbarOptions" @mounted="">
    <div> <!-- it's recommended to use a wrapper for your content -->
      Component
    </div>
  </Scrollbar>
</template>

<script lang="ts" setup>
import { ref } from "vue"
import { Scrollbar, type ScrollbarOptions } from "@smooth-scrollbar-contrib/vue-wrapper"

const scrollbarRef = ref<InstanceType<typeof Scrollbar> | null>(null)

const scrollbarOptions: ScrollbarOptions = {
  delegateTo: typeof document !== 'undefined' ? document : null, // if you are using ssr
  damping: 0.2
}
</script>
```

With `component` you have these features:

- Exposed `scrollbar`, access the scrollbar instance at the place you used the Component, with [Ref on Component](https://vuejs.org/guide/essentials/template-refs.html#ref-on-component)
- [Provide / Inject](https://vuejs.org/guide/components/provide-inject.html), access scrollbar instance inside child components with `useScrollbarInject` composable
- Watch `target` changes like `v-if`, destroy and re-init automatically

### Props

| Name       | Description | Default | Type |
| ----------- | ----------- | ----------- | ----------- |
| `as`      | The element that the component should be rendered as | `div` | `string`
| `scrollbar-options`   |  Options for Scrollbar, please refer to [`smooth-scrollbar`](https://github.com/idiotWu/smooth-scrollbar/tree/develop/docs#available-options-for-scrollbar) documentation        | `undefined`        | `ScrollbarOptions` |

### Emit

| Name       | Description | Payload | Type |
| ----------- | ----------- | ----------- | ----------- |
| `mounted` |  Scrollbar mounted  | `{ target, scrollbar }` | `MountedEmitPayload` |

> **Note**: If you need other lifecycle methods you can use Vue [vnode lifecycle events/hooks](https://v3-migration.vuejs.org/breaking-changes/vnode-lifecycle-events.html) for `beforeMount` and `beforeUnmount / beforeDestroy`

Vue3: `@vue:beforeMount / @vnode-before-mount` and `@vue:beforeUnmount / @vnode-before-unmount`

Vue2: `@hook:beforeMount` and `@hook:beforeDestroy`


### Expose

| Name       | Description         | Type   |
| ---------- | ------------------- | ------ |
| `scrollbar` | `smooth-scrollbar` instance | `object` |

<details>
<summary>Composables</summary>

#### useScrollbar
Create `smooth-scrollbar` with composable
```vue
<template>
  <div ref="target">
    <div> <!-- it's recommended to use a wrapper for your content -->
      Composable
    </div>
  </div>
</template>

<script lang="ts" setup>
import { ref } from "vue"
import { useScrollbar } from "@smooth-scrollbar-contrib/vue-wrapper"

const target = ref<HTMLElement | null>(null)

const scrollbar = useScrollbar(target, {
  delegateTo: typeof document !== 'undefined' ? document : null, // if you are using ssr
  damping: 0.2
})
</script>
```

#### useScrollbarInject
Inject `smooth-scrollbar` instance to component's descendants

```vue
<script lang="ts" setup>
import { useScrollbarInject } from "@smooth-scrollbar-contrib/vue-wrapper"

const scrollbar = useScrollbarInject()
</script>
```

</details>

<details>
<summary>Directive</summary>

Vue directive for `smooth-scrollbar` with [CustomEvents](https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent/CustomEvent)

> **Warning**: Custom directive intended to use only on client-side

```vue
<template>
  <div v-scrollbar="{
    damping: 0.2
  }"
  @scrollbar-mounted="mountedFn"
  @scrollbar-unmounted="unmountedFn"
  >
    <div> <!-- it's recommended to use a wrapper for your content -->
      Directive
    </div>
  </div>
</template>

<script lang="ts" setup>
import type SmoothScrollbar from 'smooth-scrollbar'
import { vScrollbar } from "@smooth-scrollbar-contrib/vue-wrapper"

function mountedFn(event: CustomEvent<SmoothScrollbar>) {
  console.log(event.detail) // smooth-scrollbar instance
}

function unmountedFn(event: CustomEvent<HTMLElement>) {
  console.log(event.detail) // smooth-scrollbar element
}
</script>
```

### CustomEvents

| Name | Description | Callback (detail) |
| ----------- | ----------- | ----------- |
| `scrollbar-mounted` | fired when the DOM element is inserted and the library is loaded on it | `smooth-scrollbar` instance |
| `scrollbar-unmounted` |  fired when the DOM element is unbound and the library is unloaded. | `smooth-scrollbar` element |

</details>



## Limitation

- Vue ≤ 2.6 has partial TypeScript support in templates, also [`@vue/composition-api`](https://github.com/vuejs/composition-api) reached End of Life, so it's better to upgrade your app to 2.7 or 3.2
- [`Volar` or `Vetur`](https://github.com/vuejs/language-tools/tree/master/packages/vscode-vue#usage)? Vue team recommends using `Volar`, however, you must set `vueCompilerOptions` in your tsconfig.json file

```
{
  "compilerOptions": {
    // ...
  },
  "vueCompilerOptions": {
    "target": 2.7,
    // "target": 2, // For Vue version <= 2.6.14
  }
}
```

- Custom directive intended to use only on client-side, though it can configure to use in SSR app

  <details>
  <summary>SSR directives example in Nuxt3</summary>

  ```ts
  import { defineNuxtPlugin } from '#app';
  import { vScrollbar } from '@smooth-scrollbar-contrib/vue-wrapper'

  export default defineNuxtPlugin((app) => {
    app.vueApp.directive('vScrollbar', {
      ...vScrollbar,
      getSSRProps(binding, vnode) {
        return {};
      }
    });
  })
  ```

  </details>

- [`unbuild`](https://github.com/unjs/unbuild) (The package that builds this project) uses ES2020 as compiler target by default, If you are using older tools like `Vue CLI ≤ 4` and use `import` instead of `require` you have to take care about backward-compatible JavaScript code

  <details>
  <summary>Vue CLI 4 (webpack 4)</summary>

  ```js
  // vue.config.js

  module.exports = {
    transpileDependencies: ['@smooth-scrollbar-contrib/vue-wrapper'],
  }
  ```
  </details>

  <details>
  <summary>Vue CLI 3 (webpack 4)</summary>

  ```js
  // vue.config.js

  module.exports = {
    transpileDependencies: ['@smooth-scrollbar-contrib/vue-wrapper'],
    chainWebpack(config) {
      // Getting webpack to recognize the `.mjs` file
      config.module
        .rule('mjs')
        .include.add(/node_modules/)
        .type('javascript/auto')
        .end()
    },
  }
  ```
  </details>

## Best Practice

- Use **wrapper** element for your `content` or `slot` inside Scrollbar component
- Use `shallowRef` for Template Refs
- Use `plain object` over `ref` object for Scrollbar options, cause `smooth-scrollbar` options are read-only **after** Scrollbar is initialized
- Use [`v-memo`](https://vuejs.org/api/built-in-directives.html#v-memo), If you are using Scrollbar component inside your _custom component_ (that has many props or states) to skip **re-render**, when it's not needed
- Use **local registration** instead of global registration


## CLI

This package is using simple `postinstall` script and `cli` in order to get the right Vue build for user

### Manually Switch Versions

To explicitly switch the redirecting version, you can use these commands in your project's root.

```bash
npx smooth-scrollbar-vue-switch 2.6
# or
npx smooth-scrollbar-vue-switch 2.7
# or
npx smooth-scrollbar-vue-switch 3
```

### Auto Fix

If the `postinstall` hook doesn't get triggered or you have updated the Vue version, try to run the following command to resolve the redirecting.

```bash
npx smooth-scrollbar-vue-fix
```

## Credits

- [vue-demi](https://github.com/vueuse/vue-demi)
- [smooth-vuebar](https://github.com/scaccogatto/smooth-vuebar)
- [json-editor-vue](https://github.com/cloydlau/json-editor-vue)

## License
