Skip to content

Commit 5ba019d

Browse files
authored
Merge pull request #8 from microcipcip/feature/useSearchParam
feat(useSearchParams): Adding useSearchParams function
2 parents ab180cf + 260b3f8 commit 5ba019d

21 files changed

+434
-68
lines changed

README.md

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,16 @@
1818

1919
> 🛠️ Vue kit of useful [Vue Composition API](https://vue-composition-api-rfc.netlify.com) functions.</em>
2020
21-
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.
22-
2321
## Install
2422

2523
```shell script
26-
npm install @vue/composition-api vue-use-kit
24+
npm install vue-use-kit
25+
```
26+
27+
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.
28+
29+
```shell script
30+
npm install @vue/composition-api
2731
```
2832

2933
## Setup
@@ -78,6 +82,8 @@ Vue.use(VueCompositionAPI);
7882
[![Demo](https://img.shields.io/badge/advanced_demo-🚀-yellow.svg)](https://microcipcip.github.io/vue-use-kit/?path=/story/sensors-usemouse--advanced-demo)
7983
- [`useMouseElement`](./src/components/useMouseElement/stories/useMouseElement.md) &mdash; tracks the mouse position relative to given element.
8084
[![Demo](https://img.shields.io/badge/demo-🚀-yellow.svg)](https://microcipcip.github.io/vue-use-kit/?path=/story/sensors-usemouseelement--demo)
85+
- [`useSearchParams`](./src/components/useSearchParams/stories/useSearchParams.md) &mdash; tracks browser's location search params.
86+
[![Demo](https://img.shields.io/badge/demo-🚀-yellow.svg)](https://microcipcip.github.io/vue-use-kit/?path=/story/sensors-usesearchparams--demo)
8187
- Animations
8288
- [`useInterval`](./src/components/useInterval/stories/useInterval.md) &mdash; updates `counter` value repeatedly on a fixed time delay.
8389
[![Demo](https://img.shields.io/badge/demo-🚀-yellow.svg)](https://microcipcip.github.io/vue-use-kit/?path=/story/animations-useinterval--demo)

src/components/useClickAway/useClickAway.spec.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@ const testComponent = () => ({
2828
describe('useClickAway', () => {
2929
it('should call document.addEventListener', async () => {
3030
const addEventListenerSpy = jest.spyOn(document, 'addEventListener')
31-
expect(addEventListenerSpy).not.toHaveBeenCalled()
3231
const removeEventListenerSpy = jest.spyOn(document, 'removeEventListener')
3332
const wrapper = mount(testComponent())
3433
await wrapper.vm.$nextTick()

src/components/useIdle/stories/UseIdleDemo.vue

Lines changed: 4 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,12 @@
1515
<td colspan="2">
1616
<button
1717
class="button is-primary"
18-
@click="startTracking"
18+
@click="start"
1919
v-if="!isTracking"
2020
>
2121
Start tracking idle status
2222
</button>
23-
<button class="button is-danger" @click="stopTracking" v-else>
23+
<button class="button is-danger" @click="stop" v-else>
2424
Stop tracking idle status
2525
</button>
2626
</td>
@@ -37,19 +37,8 @@ import { useIdle } from '@src/vue-use-kit'
3737
export default Vue.extend({
3838
name: 'UseIdleDemo',
3939
setup() {
40-
const { isIdle, start, stop } = useIdle(2500)
41-
42-
const isTracking = ref(true)
43-
const startTracking = () => {
44-
isTracking.value = true
45-
start()
46-
}
47-
const stopTracking = () => {
48-
isTracking.value = false
49-
stop()
50-
}
51-
52-
return { isIdle, isTracking, startTracking, stopTracking }
40+
const { isIdle, isTracking, start, stop } = useIdle(2500)
41+
return { isIdle, isTracking, start, stop }
5342
}
5443
})
5544
</script>

src/components/useIdle/stories/useIdle.md

Lines changed: 6 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ function useIdle(
1111
runOnMount?: boolean
1212
): {
1313
isIdle: Ref<boolean>;
14+
isTracking: Ref<boolean>;
1415
start: () => void;
1516
stop: () => void;
1617
}
@@ -25,6 +26,7 @@ function useIdle(
2526
### Returns
2627

2728
- `isIdle: Ref<boolean>` it is `true` when the user is idle, `false` otherwise
29+
- `isTracking: Ref<boolean>` whether the function is tracking the user idle state or not
2830
- `start: Function` the function used for start tracking the user's idle state
2931
- `stop: Function` the function used for stop tracking the user's idle state
3032

@@ -34,8 +36,8 @@ function useIdle(
3436
<template>
3537
<div>
3638
<p>isIdle: {{ isIdle }}</p>
37-
<button @click="startTracking" v-if="!isTracking">Start tracking</button>
38-
<button @click="stopTracking" v-else>Stop tracking</button>
39+
<button @click="start" v-if="!isTracking">Start tracking</button>
40+
<button @click="stop" v-else>Stop tracking</button>
3941
</div>
4042
</template>
4143

@@ -46,19 +48,8 @@ function useIdle(
4648
export default Vue.extend({
4749
name: 'UseIdleDemo',
4850
setup() {
49-
const { isIdle, start, stop } = useIdle(2500)
50-
51-
const isTracking = ref(true)
52-
const startTracking = () => {
53-
isTracking.value = true
54-
start()
55-
}
56-
const stopTracking = () => {
57-
isTracking.value = false
58-
stop()
59-
}
60-
61-
return { isIdle, isTracking, startTracking, stopTracking }
51+
const { isIdle, isTracking, start, stop } = useIdle(2500)
52+
return { isIdle, isTracking, start, stop }
6253
}
6354
})
6455
</script>

src/components/useIdle/useIdle.spec.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ describe('useIdle', () => {
2525

2626
it('should call document.addEventListener', async () => {
2727
const addEventListenerSpy = jest.spyOn(document, 'addEventListener')
28-
expect(addEventListenerSpy).not.toHaveBeenCalled()
2928
const removeEventListenerSpy = jest.spyOn(document, 'removeEventListener')
3029
const wrapper = mount(testComponent())
3130
await wrapper.vm.$nextTick()

src/components/useIdle/useIdle.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ export function useIdle(
1818
) {
1919
let timeout: any = null
2020
const isIdle = ref(false)
21+
const isTracking = ref(false)
2122

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

3637
const start = () => {
38+
if (isTracking.value) return
3739
events.forEach(evtName => document.addEventListener(evtName, handleChange))
3840
document.addEventListener('visibilitychange', handleVisibility)
3941

4042
// Initialize it since the events above may not run immediately
4143
handleChange()
44+
isTracking.value = true
4245
}
4346

4447
const stop = () => {
48+
if (!isTracking.value) return
4549
events.forEach(evtName =>
4650
document.removeEventListener(evtName, handleChange)
4751
)
@@ -53,10 +57,11 @@ export function useIdle(
5357
// Restore initial status
5458
timeout = null
5559
isIdle.value = false
60+
isTracking.value = false
5661
}
5762

5863
onMounted(() => runOnMount && start())
5964
onUnmounted(stop)
6065

61-
return { isIdle, start, stop }
66+
return { isIdle, isTracking, start, stop }
6267
}

src/components/useLocation/useLocation.spec.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,6 @@ describe('useLocation', () => {
3939

4040
it('should call popstate, pushstate and replacestate onMounted', async () => {
4141
const addEventListenerSpy = jest.spyOn(window, 'addEventListener')
42-
expect(addEventListenerSpy).not.toHaveBeenCalled()
4342
const removeEventListenerSpy = jest.spyOn(window, 'removeEventListener')
4443
const wrapper = mount(testComponent())
4544
await wrapper.vm.$nextTick()

src/components/useLocation/useLocation.ts

Lines changed: 4 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { ref, onMounted, onUnmounted, Ref } from '@src/api'
2+
import { patchHistoryMethodsOnce } from '@src/utils'
23

34
export interface UseLocationState {
45
trigger: string
@@ -15,28 +16,6 @@ export interface UseLocationState {
1516
search: string
1617
}
1718

18-
// The history methods 'pushState' and 'replaceState' by default do not fire an event
19-
// unless it is coming from user interaction with the browser navigation bar,
20-
// so we are adding a patch to make them detectable
21-
let isPatched = false
22-
const patchHistoryMethodsOnce = () => {
23-
if (isPatched) return
24-
const methods = ['pushState', 'replaceState']
25-
methods.forEach(method => {
26-
const original = (history as any)[method]
27-
;(history as any)[method] = function(state: any) {
28-
// eslint-disable-next-line prefer-rest-params
29-
const result = original.apply(this, arguments)
30-
const event = new Event(method.toLowerCase())
31-
;(event as any).state = state
32-
window.dispatchEvent(event)
33-
return result
34-
}
35-
})
36-
37-
isPatched = true
38-
}
39-
4019
export function useLocation(runOnMount = true) {
4120
const buildState = (trigger: string) => {
4221
const { state, length } = history
@@ -76,23 +55,21 @@ export function useLocation(runOnMount = true) {
7655
const replaceState = () => (locationState.value = buildState('replacestate'))
7756

7857
const start = () => {
79-
patchHistoryMethodsOnce()
80-
8158
if (isTracking.value) return
82-
isTracking.value = true
83-
59+
patchHistoryMethodsOnce()
8460
locationState.value = buildState('start')
8561
window.addEventListener('popstate', popState)
8662
window.addEventListener('pushstate', pushState)
8763
window.addEventListener('replacestate', replaceState)
64+
isTracking.value = true
8865
}
8966

9067
const stop = () => {
9168
if (!isTracking.value) return
92-
isTracking.value = false
9369
window.removeEventListener('popstate', popState)
9470
window.removeEventListener('pushstate', pushState)
9571
window.removeEventListener('replacestate', replaceState)
72+
isTracking.value = false
9673
}
9774

9875
onMounted(() => runOnMount && start())

src/components/useMouse/useMouse.spec.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ const testComponent = () => ({
2121
describe('useMouse', () => {
2222
it('should call document.addEventListener', async () => {
2323
const addEventListenerSpy = jest.spyOn(document, 'addEventListener')
24-
expect(addEventListenerSpy).not.toHaveBeenCalled()
2524
const removeEventListenerSpy = jest.spyOn(document, 'removeEventListener')
2625
const wrapper = mount(testComponent())
2726
await wrapper.vm.$nextTick()

src/components/useMouseElement/useMouseElement.spec.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,6 @@ const testComponent = () => ({
3838
describe('useMouseElement', () => {
3939
it('should call document.addEventListener', async () => {
4040
const addEventListenerSpy = jest.spyOn(document, 'addEventListener')
41-
expect(addEventListenerSpy).not.toHaveBeenCalled()
4241
const removeEventListenerSpy = jest.spyOn(document, 'removeEventListener')
4342
const wrapper = mount(testComponent())
4443
await wrapper.vm.$nextTick()
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './useSearchParams'
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<template>
2+
<div class="field is-horizontal">
3+
<div class="field-label is-normal">
4+
<label class="label">{{label}}</label>
5+
</div>
6+
<div class="field-body">
7+
<div class="field">
8+
<slot></slot>
9+
</div>
10+
</div>
11+
</div>
12+
</template>
13+
14+
<script>
15+
export default {
16+
name: 'Field',
17+
props: {
18+
label: String,
19+
}
20+
}
21+
</script>
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
<template>
2+
<table class="table is-fullwidth">
3+
<thead>
4+
<tr>
5+
<th>Prop</th>
6+
<th>Value</th>
7+
</tr>
8+
</thead>
9+
<tbody>
10+
<tr>
11+
<td>searchParams</td>
12+
<td>
13+
<pre>{{ searchParams }}</pre>
14+
<br />
15+
<field label="Search param">
16+
<input class="input" type="text" v-model="searchFld" />
17+
</field>
18+
<field label="Filter param">
19+
<input class="input" type="text" v-model="filterFld" />
20+
</field>
21+
<button class="button is-info" @click="clearFields">Clear</button>
22+
</td>
23+
</tr>
24+
<tr>
25+
<td colspan="2">
26+
<button class="button is-primary" @click="start" v-if="!isTracking">
27+
Start tracking search param
28+
</button>
29+
<button class="button is-danger" @click="stop" v-else>
30+
Stop tracking search param
31+
</button>
32+
</td>
33+
</tr>
34+
</tbody>
35+
</table>
36+
</template>
37+
38+
<script lang="ts">
39+
import Vue from 'vue'
40+
import { ref, watch } from '@src/api'
41+
import { useSearchParams } from '@src/vue-use-kit'
42+
import Field from './Field.vue'
43+
44+
const updateSearchParams = (
45+
url: string,
46+
idVal: string | null,
47+
searchVal: string
48+
) => history.pushState({}, '', `${url}?id=${idVal}${searchVal}`)
49+
50+
export default Vue.extend({
51+
name: 'UseSearchParamsDemo',
52+
components: { Field },
53+
setup() {
54+
const { searchParams, isTracking, start, stop } = useSearchParams([
55+
'id',
56+
'search',
57+
'filter'
58+
])
59+
60+
const searchFld = ref('')
61+
const filterFld = ref('')
62+
// Update location bar params
63+
watch([searchFld, filterFld], ([searchVal, filterVal]) => {
64+
const url = `${location.origin}${location.pathname}`
65+
const idVal = new URLSearchParams(location.search).get('id')
66+
const fieldParams =
67+
searchVal || filterVal ? `&search=${searchVal}&filter=${filterVal}` : ''
68+
updateSearchParams(url, idVal, fieldParams)
69+
})
70+
71+
const clearFields = () => {
72+
searchFld.value = ''
73+
filterFld.value = ''
74+
}
75+
76+
return {
77+
searchParams,
78+
isTracking,
79+
start,
80+
stop,
81+
searchFld,
82+
filterFld,
83+
clearFields
84+
}
85+
}
86+
})
87+
</script>

0 commit comments

Comments
 (0)