Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 9 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,16 @@

> 🛠️ Vue kit of useful [Vue Composition API](https://vue-composition-api-rfc.netlify.com) functions.</em>

Please note that Vue 3.0 has not been released yet, therefore the installation and setup of [@vue/composition-api](https://github.com/vuejs/composition-api) is required for this library to work.

## Install

```shell script
npm install @vue/composition-api vue-use-kit
npm install vue-use-kit
```

Since Vue 3.0 has not yet been released, you must also install [@vue/composition-api](https://github.com/vuejs/composition-api) library, which will enable the composition API in Vue 2.0.

```shell script
npm install @vue/composition-api
```

## Setup
Expand Down Expand Up @@ -78,6 +82,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) &mdash; 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)
- [`useSearchParams`](./src/components/useSearchParams/stories/useSearchParams.md) &mdash; 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
- [`useInterval`](./src/components/useInterval/stories/useInterval.md) &mdash; updates `counter` value repeatedly on a fixed time delay.
[![Demo](https://img.shields.io/badge/demo-🚀-yellow.svg)](https://microcipcip.github.io/vue-use-kit/?path=/story/animations-useinterval--demo)
Expand Down
1 change: 0 additions & 1 deletion src/components/useClickAway/useClickAway.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ const testComponent = () => ({
describe('useClickAway', () => {
it('should call document.addEventListener', async () => {
const addEventListenerSpy = jest.spyOn(document, 'addEventListener')
expect(addEventListenerSpy).not.toHaveBeenCalled()
const removeEventListenerSpy = jest.spyOn(document, 'removeEventListener')
const wrapper = mount(testComponent())
await wrapper.vm.$nextTick()
Expand Down
19 changes: 4 additions & 15 deletions src/components/useIdle/stories/UseIdleDemo.vue
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,12 @@
<td colspan="2">
<button
class="button is-primary"
@click="startTracking"
@click="start"
v-if="!isTracking"
>
Start tracking idle status
</button>
<button class="button is-danger" @click="stopTracking" v-else>
<button class="button is-danger" @click="stop" v-else>
Stop tracking idle status
</button>
</td>
Expand All @@ -37,19 +37,8 @@ import { useIdle } from '@src/vue-use-kit'
export default Vue.extend({
name: 'UseIdleDemo',
setup() {
const { isIdle, start, stop } = useIdle(2500)

const isTracking = ref(true)
const startTracking = () => {
isTracking.value = true
start()
}
const stopTracking = () => {
isTracking.value = false
stop()
}

return { isIdle, isTracking, startTracking, stopTracking }
const { isIdle, isTracking, start, stop } = useIdle(2500)
return { isIdle, isTracking, start, stop }
}
})
</script>
Expand Down
21 changes: 6 additions & 15 deletions src/components/useIdle/stories/useIdle.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ function useIdle(
runOnMount?: boolean
): {
isIdle: Ref<boolean>;
isTracking: Ref<boolean>;
start: () => void;
stop: () => void;
}
Expand All @@ -25,6 +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
- `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 All @@ -34,8 +36,8 @@ function useIdle(
<template>
<div>
<p>isIdle: {{ isIdle }}</p>
<button @click="startTracking" v-if="!isTracking">Start tracking</button>
<button @click="stopTracking" v-else>Stop tracking</button>
<button @click="start" v-if="!isTracking">Start tracking</button>
<button @click="stop" v-else>Stop tracking</button>
</div>
</template>

Expand All @@ -46,19 +48,8 @@ function useIdle(
export default Vue.extend({
name: 'UseIdleDemo',
setup() {
const { isIdle, start, stop } = useIdle(2500)

const isTracking = ref(true)
const startTracking = () => {
isTracking.value = true
start()
}
const stopTracking = () => {
isTracking.value = false
stop()
}

return { isIdle, isTracking, startTracking, stopTracking }
const { isIdle, isTracking, start, stop } = useIdle(2500)
return { isIdle, isTracking, start, stop }
}
})
</script>
Expand Down
1 change: 0 additions & 1 deletion src/components/useIdle/useIdle.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ describe('useIdle', () => {

it('should call document.addEventListener', async () => {
const addEventListenerSpy = jest.spyOn(document, 'addEventListener')
expect(addEventListenerSpy).not.toHaveBeenCalled()
const removeEventListenerSpy = jest.spyOn(document, 'removeEventListener')
const wrapper = mount(testComponent())
await wrapper.vm.$nextTick()
Expand Down
7 changes: 6 additions & 1 deletion src/components/useIdle/useIdle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export function useIdle(
) {
let timeout: any = null
const isIdle = ref(false)
const isTracking = ref(false)

const handleChange = throttle(50, () => {
isIdle.value = false
Expand All @@ -34,14 +35,17 @@ export function useIdle(
}

const start = () => {
if (isTracking.value) return
events.forEach(evtName => document.addEventListener(evtName, handleChange))
document.addEventListener('visibilitychange', handleVisibility)

// Initialize it since the events above may not run immediately
handleChange()
isTracking.value = true
}

const stop = () => {
if (!isTracking.value) return
events.forEach(evtName =>
document.removeEventListener(evtName, handleChange)
)
Expand All @@ -53,10 +57,11 @@ export function useIdle(
// Restore initial status
timeout = null
isIdle.value = false
isTracking.value = false
}

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

return { isIdle, start, stop }
return { isIdle, isTracking, start, stop }
}
1 change: 0 additions & 1 deletion src/components/useLocation/useLocation.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ describe('useLocation', () => {

it('should call popstate, pushstate and replacestate onMounted', async () => {
const addEventListenerSpy = jest.spyOn(window, 'addEventListener')
expect(addEventListenerSpy).not.toHaveBeenCalled()
const removeEventListenerSpy = jest.spyOn(window, 'removeEventListener')
const wrapper = mount(testComponent())
await wrapper.vm.$nextTick()
Expand Down
31 changes: 4 additions & 27 deletions src/components/useLocation/useLocation.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { ref, onMounted, onUnmounted, Ref } from '@src/api'
import { patchHistoryMethodsOnce } from '@src/utils'

export interface UseLocationState {
trigger: string
Expand All @@ -15,28 +16,6 @@ export interface UseLocationState {
search: string
}

// The history methods 'pushState' and 'replaceState' by default do not fire an event
// unless it is coming from user interaction with the browser navigation bar,
// so we are adding a patch to make them detectable
let isPatched = false
const patchHistoryMethodsOnce = () => {
if (isPatched) return
const methods = ['pushState', 'replaceState']
methods.forEach(method => {
const original = (history as any)[method]
;(history as any)[method] = function(state: any) {
// eslint-disable-next-line prefer-rest-params
const result = original.apply(this, arguments)
const event = new Event(method.toLowerCase())
;(event as any).state = state
window.dispatchEvent(event)
return result
}
})

isPatched = true
}

export function useLocation(runOnMount = true) {
const buildState = (trigger: string) => {
const { state, length } = history
Expand Down Expand Up @@ -76,23 +55,21 @@ export function useLocation(runOnMount = true) {
const replaceState = () => (locationState.value = buildState('replacestate'))

const start = () => {
patchHistoryMethodsOnce()

if (isTracking.value) return
isTracking.value = true

patchHistoryMethodsOnce()
locationState.value = buildState('start')
window.addEventListener('popstate', popState)
window.addEventListener('pushstate', pushState)
window.addEventListener('replacestate', replaceState)
isTracking.value = true
}

const stop = () => {
if (!isTracking.value) return
isTracking.value = false
window.removeEventListener('popstate', popState)
window.removeEventListener('pushstate', pushState)
window.removeEventListener('replacestate', replaceState)
isTracking.value = false
}

onMounted(() => runOnMount && start())
Expand Down
1 change: 0 additions & 1 deletion src/components/useMouse/useMouse.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ const testComponent = () => ({
describe('useMouse', () => {
it('should call document.addEventListener', async () => {
const addEventListenerSpy = jest.spyOn(document, 'addEventListener')
expect(addEventListenerSpy).not.toHaveBeenCalled()
const removeEventListenerSpy = jest.spyOn(document, 'removeEventListener')
const wrapper = mount(testComponent())
await wrapper.vm.$nextTick()
Expand Down
1 change: 0 additions & 1 deletion src/components/useMouseElement/useMouseElement.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ const testComponent = () => ({
describe('useMouseElement', () => {
it('should call document.addEventListener', async () => {
const addEventListenerSpy = jest.spyOn(document, 'addEventListener')
expect(addEventListenerSpy).not.toHaveBeenCalled()
const removeEventListenerSpy = jest.spyOn(document, 'removeEventListener')
const wrapper = mount(testComponent())
await wrapper.vm.$nextTick()
Expand Down
1 change: 1 addition & 0 deletions src/components/useSearchParams/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './useSearchParams'
21 changes: 21 additions & 0 deletions src/components/useSearchParams/stories/Field.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<template>
<div class="field is-horizontal">
<div class="field-label is-normal">
<label class="label">{{label}}</label>
</div>
<div class="field-body">
<div class="field">
<slot></slot>
</div>
</div>
</div>
</template>

<script>
export default {
name: 'Field',
props: {
label: String,
}
}
</script>
87 changes: 87 additions & 0 deletions src/components/useSearchParams/stories/UseSearchParamsDemo.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
<template>
<table class="table is-fullwidth">
<thead>
<tr>
<th>Prop</th>
<th>Value</th>
</tr>
</thead>
<tbody>
<tr>
<td>searchParams</td>
<td>
<pre>{{ searchParams }}</pre>
<br />
<field label="Search param">
<input class="input" type="text" v-model="searchFld" />
</field>
<field label="Filter param">
<input class="input" type="text" v-model="filterFld" />
</field>
<button class="button is-info" @click="clearFields">Clear</button>
</td>
</tr>
<tr>
<td colspan="2">
<button class="button is-primary" @click="start" v-if="!isTracking">
Start tracking search param
</button>
<button class="button is-danger" @click="stop" v-else>
Stop tracking search param
</button>
</td>
</tr>
</tbody>
</table>
</template>

<script lang="ts">
import Vue from 'vue'
import { ref, watch } from '@src/api'
import { useSearchParams } from '@src/vue-use-kit'
import Field from './Field.vue'

const updateSearchParams = (
url: string,
idVal: string | null,
searchVal: string
) => history.pushState({}, '', `${url}?id=${idVal}${searchVal}`)

export default Vue.extend({
name: 'UseSearchParamsDemo',
components: { Field },
setup() {
const { searchParams, isTracking, start, stop } = useSearchParams([
'id',
'search',
'filter'
])

const searchFld = ref('')
const filterFld = ref('')
// Update location bar params
watch([searchFld, filterFld], ([searchVal, filterVal]) => {
const url = `${location.origin}${location.pathname}`
const idVal = new URLSearchParams(location.search).get('id')
const fieldParams =
searchVal || filterVal ? `&search=${searchVal}&filter=${filterVal}` : ''
updateSearchParams(url, idVal, fieldParams)
})

const clearFields = () => {
searchFld.value = ''
filterFld.value = ''
}

return {
searchParams,
isTracking,
start,
stop,
searchFld,
filterFld,
clearFields
}
}
})
</script>
Loading