Skip to content

Commit

Permalink
feat(useMouseLeavePage): Adding useMouseLeavePage function
Browse files Browse the repository at this point in the history
  • Loading branch information
microcipcip committed Feb 22, 2020
1 parent eb01041 commit 5e1747c
Show file tree
Hide file tree
Showing 12 changed files with 263 additions and 4 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,8 @@ Vue.use(VueCompositionAPI);
[![Demo](https://img.shields.io/badge/advanced_demo-馃殌-yellow.svg)](https://microcipcip.github.io/vue-use-kit/?path=/story/sensors-usemouse--advanced-demo)
- [`useMouseElement`](./src/components/useMouseElement/stories/useMouseElement.md) — tracks the mouse position relative to given element.
[![Demo](https://img.shields.io/badge/demo-馃殌-yellow.svg)](https://microcipcip.github.io/vue-use-kit/?path=/story/sensors-usemouseelement--demo)
- [`useMouseLeavePage`](./src/components/useMouseLeavePage/stories/useMouseLeavePage.md) — tracks when mouse leaves page boundaries.
[![Demo](https://img.shields.io/badge/demo-馃殌-yellow.svg)](https://microcipcip.github.io/vue-use-kit/?path=/story/sensors-usemouseleavepage--demo)
- [`useSearchParams`](./src/components/useSearchParams/stories/useSearchParams.md) — tracks browser's location search params.
[![Demo](https://img.shields.io/badge/demo-馃殌-yellow.svg)](https://microcipcip.github.io/vue-use-kit/?path=/story/sensors-usesearchparams--demo)
- Animations
Expand Down
2 changes: 1 addition & 1 deletion src/components/useGeolocation/stories/useGeolocation.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ function useGeolocation(
### Returns

- `geo: Ref<UseGeolocation>` the geolocation object
- `isTracking: Ref<boolean>` whether the function is tracking the user's location or not
- `isTracking: Ref<boolean>` whether this function events are running or not
- `start: Function` the function used for starting the geolocation tracking
- `stop: Function` the function used for stopping the geolocation tracking

Expand Down
2 changes: 1 addition & 1 deletion src/components/useIdle/stories/useIdle.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ function useIdle(
### Returns

- `isIdle: Ref<boolean>` it is `true` when the user is idle, `false` otherwise
- `isTracking: Ref<boolean>` whether the function is tracking the user idle state or not
- `isTracking: Ref<boolean>` whether this function events are running or not
- `start: Function` the function used for start tracking the user's idle state
- `stop: Function` the function used for stop tracking the user's idle state

Expand Down
2 changes: 1 addition & 1 deletion src/components/useMediaDevices/stories/useMediaDevices.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ function useMediaDevices(
### Returns

- `devicesState: Ref<UseMediaDevicesState[]>` the list of connected media devices
- `isTracking: Ref<boolean>` whether the function is tracking the connected media devices or not
- `isTracking: Ref<boolean>` whether this function events are running or not
- `isTracked: Ref<boolean>` whether the connected devices have been successfully tracked
- `start: Function` the function used to start tracking the connected media devices
- `stop: Function` the function used to stop tracking the connected media devices
Expand Down
1 change: 1 addition & 0 deletions src/components/useMouseLeavePage/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './useMouseLeavePage'
47 changes: 47 additions & 0 deletions src/components/useMouseLeavePage/stories/UseMouseLeavePageDemo.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<template>
<div class="wrapper">
<table class="table is-fullwidth">
<thead>
<tr>
<th>Prop</th>
<th>Value</th>
</tr>
</thead>
<tbody>
<tr>
<td>hasLeftPage</td>
<td>{{ hasLeftPage }}</td>
</tr>
<tr>
<td colspan="2">
<button class="button is-primary" @click="start" v-if="!isTracking">
Start tracking mouse leave event
</button>
<button class="button is-danger" @click="stop" v-else>
Stop tracking mouse leave event
</button>
</td>
</tr>
</tbody>
</table>
</div>
</template>

<script lang="ts">
import Vue from 'vue'
import { useMouseLeavePage } from '@src/vue-use-kit'
export default Vue.extend({
name: 'UseMouseLeavePageDemo',
setup() {
const { hasLeftPage, isTracking, start, stop } = useMouseLeavePage()
return { hasLeftPage, isTracking, start, stop }
}
})
</script>

<style scoped>
.wrapper {
min-height: 100vh;
}
</style>
59 changes: 59 additions & 0 deletions src/components/useMouseLeavePage/stories/useMouseLeavePage.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# useMouseLeavePage

Vue function that tracks when mouse leaves page boundaries.

## Reference

```typescript
function useMouseLeavePage(
runOnMount?: boolean
): {
hasLeftPage: Ref<boolean>
isTracking: Ref<boolean>
start: () => void
stop: () => void
}
```

### Parameters

- `runOnMount: boolean` whether to check mouse leaves page boundaries tracking on mount, `true` by default

### Returns

- `hasLeftPage: Ref<boolean>` whether the mouse has left the page or not
- `isTracking: Ref<boolean>` whether this function events are running or not
- `start: Function` the function used to start tracking when the mouse leaves the page boundaries
- `stop: Function` the function used to stop tracking when the mouse leaves the page boundaries

## Usage

```html
<template>
<div>
<div>
hasLeftPage:
<pre>{{ hasLeftPage }}</pre>
</div>
<div>
<button @click="start" v-if="!isTracking">
Start tracking mouse leave event
</button>
<button @click="stop" v-else>Stop tracking mouse leave event</button>
</div>
</div>
</template>

<script lang="ts">
import Vue from 'vue'
import { useMouseLeavePage } from 'vue-use-kit'
export default Vue.extend({
name: 'UseMouseLeavePageDemo',
setup() {
const { hasLeftPage, isTracking, start, stop } = useMouseLeavePage()
return { hasLeftPage, isTracking, start, stop }
}
})
</script>
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { storiesOf } from '@storybook/vue'
import path from 'path'
import StoryTitle from '@src/helpers/StoryTitle.vue'
import UseMouseLeavePageDemo from './UseMouseLeavePageDemo.vue'

const functionName = 'useMouseLeavePage'
const functionPath = path.resolve(__dirname, '..')
const notes = require(`./${functionName}.md`).default

const basicDemo = () => ({
components: { StoryTitle, demo: UseMouseLeavePageDemo },
template: `
<div class="container">
<story-title
function-path="${functionPath}"
source-name="${functionName}"
demo-name="UseMouseLeavePageDemo.vue"
>
<template v-slot:title></template>
<template v-slot:intro>
<p>
<strong>Try moving the mouse outside the page boundaries</strong> to see the
hasLeftPage variable change on the fly.
</p>
<p>
Please note that because this demo is within an iframe
<strong>it will seem that the callback is firing when you are still within the page.</strong>
Click the "Open canvas in new tab" button on the
top right hand corner to see the demo without iframe.
</p>
</template>
</story-title>
<demo />
</div>`
})

storiesOf('sensors|useMouseLeavePage', module)
.addParameters({ notes })
.add('Demo', basicDemo)
81 changes: 81 additions & 0 deletions src/components/useMouseLeavePage/useMouseLeavePage.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { mount } from '@src/helpers/test'
import { useMouseLeavePage } from '@src/vue-use-kit'

afterEach(() => {
jest.clearAllMocks()
})

const testComponent = (onMount = true) => ({
template: `
<div>
<div id="isTracking" v-if="isTracking"></div>
<div id="hasLeftPage" v-if="hasLeftPage"></div>
<button id="start" @click="start"></button>
<button id="stop" @click="stop"></button>
</div>
`,
setup() {
const { hasLeftPage, isTracking, start, stop } = useMouseLeavePage(onMount)
return { hasLeftPage, isTracking, start, stop }
}
})

describe('useMouseLeavePage', () => {
const eventName = 'mouseout'
it('should call mouseout onMounted', async () => {
const addEventListenerSpy = jest.spyOn(document, 'addEventListener')
const removeEventListenerSpy = jest.spyOn(document, 'removeEventListener')
const wrapper = mount(testComponent())
await wrapper.vm.$nextTick()
expect(addEventListenerSpy).toHaveBeenCalledTimes(1)
expect(addEventListenerSpy).toBeCalledWith(eventName, expect.any(Function))

// Destroy instance to check if the remove event listener is being called
wrapper.destroy()
expect(removeEventListenerSpy).toHaveBeenCalledTimes(1)
expect(removeEventListenerSpy).toBeCalledWith(
eventName,
expect.any(Function)
)
})

it('should call document.addEventListener again when start is called', async () => {
const addEventListenerSpy = jest.spyOn(document, 'addEventListener')
const wrapper = mount(testComponent())
expect(addEventListenerSpy).toHaveBeenCalledTimes(1)
wrapper.find('#stop').trigger('click')

// Wait for Vue to append #start in the DOM
await wrapper.vm.$nextTick()
wrapper.find('#start').trigger('click')
expect(addEventListenerSpy).toHaveBeenCalledTimes(1 * 2)
})

it('should call document.removeEventListener when stop is called', async () => {
const removeEventListenerSpy = jest.spyOn(document, 'removeEventListener')
const wrapper = mount(testComponent())
wrapper.find('#stop').trigger('click')

// Wait for Vue to append #start in the DOM
await wrapper.vm.$nextTick()
expect(removeEventListenerSpy).toHaveBeenCalledTimes(1)
})

it('should show #isTracking when onMount is true', async () => {
const wrapper = mount(testComponent(true))
await wrapper.vm.$nextTick()
expect(wrapper.find('#isTracking').exists()).toBe(true)
})

it('should not show #isTracking when onMount is false', async () => {
const wrapper = mount(testComponent(false))
await wrapper.vm.$nextTick()
expect(wrapper.find('#isTracking').exists()).toBe(false)
})

it('should not show #hasLeftPage when onMount is false', async () => {
const wrapper = mount(testComponent(false))
await wrapper.vm.$nextTick()
expect(wrapper.find('#hasLeftPage').exists()).toBe(false)
})
})
29 changes: 29 additions & 0 deletions src/components/useMouseLeavePage/useMouseLeavePage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { ref, onMounted, onUnmounted, Ref } from '@vue/composition-api'

export function useMouseLeavePage(runOnMount = true) {
const isTracking = ref(false)
const hasLeftPage = ref(false)

const handleMouseOut = (e: any) => {
const from = e.relatedTarget || e.toElement
const mouseHasLeftPage = !from || from.nodeName === 'HTML'
hasLeftPage.value = mouseHasLeftPage
}

const start = () => {
if (isTracking.value) return
document.addEventListener('mouseout', handleMouseOut)
isTracking.value = true
}

const stop = () => {
if (!isTracking.value) return
document.removeEventListener('mouseout', handleMouseOut)
isTracking.value = false
}

onMounted(() => runOnMount && start())
onUnmounted(stop)

return { hasLeftPage, isTracking, start, stop }
}
2 changes: 1 addition & 1 deletion src/components/useSearchParams/stories/useSearchParams.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ function useSearchParams(
### Returns

- `searchParams: Ref<object>` the object containing the search parameters as key value pairs
- `isTracking: Ref<boolean>` whether the function is tracking the user's location search parameters or not
- `isTracking: Ref<boolean>` whether this function events are running or not
- `start: Function` the function used to start tracking the location search parameters
- `stop: Function` the function used to stop tracking the location search parameters

Expand Down
1 change: 1 addition & 0 deletions src/vue-use-kit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export * from './components/useMedia'
export * from './components/useMediaDevices'
export * from './components/useMouse'
export * from './components/useMouseElement'
export * from './components/useMouseLeavePage'
export * from './components/useSearchParams'

export * from './components/useIntervalFn'
Expand Down

0 comments on commit 5e1747c

Please sign in to comment.