From 46b0a7cbee0e2a7a475afbce29839447481e849b Mon Sep 17 00:00:00 2001 From: Yuchao Wu Date: Mon, 22 Apr 2024 22:54:55 +1000 Subject: [PATCH 01/16] feat(VPullToRefresh): add new component closes #4099 --- .../labs/VPullToRefresh/VPullToRefresh.sass | 18 +++ .../labs/VPullToRefresh/VPullToRefresh.tsx | 143 ++++++++++++++++++ .../vuetify/src/labs/VPullToRefresh/index.ts | 1 + packages/vuetify/src/labs/components.ts | 1 + 4 files changed, 163 insertions(+) create mode 100644 packages/vuetify/src/labs/VPullToRefresh/VPullToRefresh.sass create mode 100644 packages/vuetify/src/labs/VPullToRefresh/VPullToRefresh.tsx create mode 100644 packages/vuetify/src/labs/VPullToRefresh/index.ts diff --git a/packages/vuetify/src/labs/VPullToRefresh/VPullToRefresh.sass b/packages/vuetify/src/labs/VPullToRefresh/VPullToRefresh.sass new file mode 100644 index 00000000000..00eda1c2919 --- /dev/null +++ b/packages/vuetify/src/labs/VPullToRefresh/VPullToRefresh.sass @@ -0,0 +1,18 @@ +.v-pull-to-refresh + overflow: hidden + &__pull-down + position: relative + display: flex + justify-content: center + align-items: flex-end + height: 64px + transition: top .3s ease-out + &--touching + transition: none + + + &__scroll-container + position: relative + transition: top .3s ease-out + &--touching + transition: none \ No newline at end of file diff --git a/packages/vuetify/src/labs/VPullToRefresh/VPullToRefresh.tsx b/packages/vuetify/src/labs/VPullToRefresh/VPullToRefresh.tsx new file mode 100644 index 00000000000..602e0e40db8 --- /dev/null +++ b/packages/vuetify/src/labs/VPullToRefresh/VPullToRefresh.tsx @@ -0,0 +1,143 @@ +// Styles +import './VPullToRefresh.sass' + +// Components +import { VIcon } from '@/components/VIcon' +import { VProgressCircular } from '@/components/VProgressCircular' + +// Composables +import { LoaderSlot } from '@/composables/loader' + +// Utilities +import { computed, onMounted, ref, shallowRef } from 'vue' +import { convertToUnit, genericComponent, useRender } from '@/util' + +const PULL_DOWN_HEIGHT_PX = 64 + +export type PullToRefreshStatus = 'ok' | 'error' + +export const VPullToRefresh = genericComponent()({ + name: 'VPullToRefresh', + + props: { + }, + + emits: { + load: (options: { done: (status: PullToRefreshStatus) => void }) => true, + }, + + setup (props, { slots, emit }) { + let touchstartY = 0 + + const touchDiff = shallowRef(0) + const scrollContainerRef = ref() + + const height = shallowRef(0) + + const canRefresh = computed(() => touchDiff.value > PULL_DOWN_HEIGHT_PX * 2 / 3) + const refreshing = shallowRef(false) + const touching = shallowRef(false) + + function onTouchstart (e: TouchEvent) { + if (refreshing.value) { + e.preventDefault() + return + } + touching.value = true + touchstartY = e.touches[0].clientY + } + + function onTouchmove (e: TouchEvent) { + if (refreshing.value) { + e.preventDefault() + return + } + const touchY = e.touches[0].clientY + if (touchDiff.value < PULL_DOWN_HEIGHT_PX && window.scrollY === 0) { + touchDiff.value = touchY - touchstartY + } + } + + function onTouchend (e: TouchEvent) { + if (refreshing.value) { + e.preventDefault() + return + } + touching.value = false + if (canRefresh.value) { + function done (status: PullToRefreshStatus) { + if (status === 'ok') { + touchDiff.value = 0 + refreshing.value = false + } + } + emit('load', { done }) + refreshing.value = true + } + } + + onMounted(() => { + height.value = scrollContainerRef.value!.offsetHeight + }) + + useRender(() => { + return ( +
+
+ { + canRefresh.value || refreshing.value ? ( + + + + ) : ( + + ) + } +
+
+ { slots.default?.() } +
+
+ ) + }) + }, +}) + +export type VPullToRefresh = InstanceType diff --git a/packages/vuetify/src/labs/VPullToRefresh/index.ts b/packages/vuetify/src/labs/VPullToRefresh/index.ts new file mode 100644 index 00000000000..343ae37299f --- /dev/null +++ b/packages/vuetify/src/labs/VPullToRefresh/index.ts @@ -0,0 +1 @@ +export { VPullToRefresh } from './VPullToRefresh' diff --git a/packages/vuetify/src/labs/components.ts b/packages/vuetify/src/labs/components.ts index 98850084f8e..68419f1de2c 100644 --- a/packages/vuetify/src/labs/components.ts +++ b/packages/vuetify/src/labs/components.ts @@ -4,6 +4,7 @@ export * from './VEmptyState' export * from './VFab' export * from './VNumberInput' export * from './VPicker' +export * from './VPullToRefresh' export * from './VSparkline' export * from './VSpeedDial' export * from './VTimePicker' From 5f0b107d177b1a8da7fd0d16c99c672cf88087b3 Mon Sep 17 00:00:00 2001 From: Yuchao Wu Date: Tue, 23 Apr 2024 21:56:49 +1000 Subject: [PATCH 02/16] chore: only trigger pull down panel when immidiate scroll parent scrolltop is 0 --- .../labs/VPullToRefresh/VPullToRefresh.sass | 5 ++-- .../labs/VPullToRefresh/VPullToRefresh.tsx | 23 +++++++++++-------- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/packages/vuetify/src/labs/VPullToRefresh/VPullToRefresh.sass b/packages/vuetify/src/labs/VPullToRefresh/VPullToRefresh.sass index 00eda1c2919..d10655bb273 100644 --- a/packages/vuetify/src/labs/VPullToRefresh/VPullToRefresh.sass +++ b/packages/vuetify/src/labs/VPullToRefresh/VPullToRefresh.sass @@ -1,11 +1,12 @@ .v-pull-to-refresh overflow: hidden + position: relative &__pull-down - position: relative + position: absolute + width: 100% display: flex justify-content: center align-items: flex-end - height: 64px transition: top .3s ease-out &--touching transition: none diff --git a/packages/vuetify/src/labs/VPullToRefresh/VPullToRefresh.tsx b/packages/vuetify/src/labs/VPullToRefresh/VPullToRefresh.tsx index 602e0e40db8..f532338d146 100644 --- a/packages/vuetify/src/labs/VPullToRefresh/VPullToRefresh.tsx +++ b/packages/vuetify/src/labs/VPullToRefresh/VPullToRefresh.tsx @@ -10,7 +10,7 @@ import { LoaderSlot } from '@/composables/loader' // Utilities import { computed, onMounted, ref, shallowRef } from 'vue' -import { convertToUnit, genericComponent, useRender } from '@/util' +import { convertToUnit, genericComponent, getScrollParents, useRender } from '@/util' const PULL_DOWN_HEIGHT_PX = 64 @@ -28,15 +28,15 @@ export const VPullToRefresh = genericComponent()({ setup (props, { slots, emit }) { let touchstartY = 0 + let lastTouchY = 0 const touchDiff = shallowRef(0) const scrollContainerRef = ref() - const height = shallowRef(0) - const canRefresh = computed(() => touchDiff.value > PULL_DOWN_HEIGHT_PX * 2 / 3) const refreshing = shallowRef(false) const touching = shallowRef(false) + let immediateScrollParent: HTMLElement | undefined function onTouchstart (e: TouchEvent) { if (refreshing.value) { @@ -44,7 +44,7 @@ export const VPullToRefresh = genericComponent()({ return } touching.value = true - touchstartY = e.touches[0].clientY + lastTouchY = touchstartY = e.touches[0].clientY + immediateScrollParent!.scrollTop } function onTouchmove (e: TouchEvent) { @@ -53,9 +53,14 @@ export const VPullToRefresh = genericComponent()({ return } const touchY = e.touches[0].clientY - if (touchDiff.value < PULL_DOWN_HEIGHT_PX && window.scrollY === 0) { + if ( + touchY > lastTouchY && + touchDiff.value < PULL_DOWN_HEIGHT_PX && + !immediateScrollParent!.scrollTop + ) { touchDiff.value = touchY - touchstartY } + lastTouchY = touchY } function onTouchend (e: TouchEvent) { @@ -77,7 +82,7 @@ export const VPullToRefresh = genericComponent()({ } onMounted(() => { - height.value = scrollContainerRef.value!.offsetHeight + immediateScrollParent = getScrollParents(scrollContainerRef.value)[0] }) useRender(() => { @@ -89,9 +94,6 @@ export const VPullToRefresh = genericComponent()({ onTouchstart={ onTouchstart } onTouchmove={ onTouchmove } onTouchend={ onTouchend } - style={{ - height: convertToUnit(height.value), - }} >
{ @@ -130,7 +133,7 @@ export const VPullToRefresh = genericComponent()({ }, ]} ref={ scrollContainerRef } - style={{ top: convertToUnit(-1 * PULL_DOWN_HEIGHT_PX + touchDiff.value) }} + style={{ top: convertToUnit(touchDiff.value) }} > { slots.default?.() }
From 77ada366a004d9ce8ad0fc87952a3e1fcb07f571 Mon Sep 17 00:00:00 2001 From: Yuchao Wu Date: Thu, 25 Apr 2024 14:09:25 +1000 Subject: [PATCH 03/16] chore: refactor to allow touchmove up to cancel --- .../labs/VPullToRefresh/VPullToRefresh.tsx | 44 +++++++++---------- 1 file changed, 21 insertions(+), 23 deletions(-) diff --git a/packages/vuetify/src/labs/VPullToRefresh/VPullToRefresh.tsx b/packages/vuetify/src/labs/VPullToRefresh/VPullToRefresh.tsx index f532338d146..07db4f6b652 100644 --- a/packages/vuetify/src/labs/VPullToRefresh/VPullToRefresh.tsx +++ b/packages/vuetify/src/labs/VPullToRefresh/VPullToRefresh.tsx @@ -10,7 +10,7 @@ import { LoaderSlot } from '@/composables/loader' // Utilities import { computed, onMounted, ref, shallowRef } from 'vue' -import { convertToUnit, genericComponent, getScrollParents, useRender } from '@/util' +import { clamp, convertToUnit, genericComponent, getScrollParents, useRender } from '@/util' const PULL_DOWN_HEIGHT_PX = 64 @@ -29,48 +29,45 @@ export const VPullToRefresh = genericComponent()({ setup (props, { slots, emit }) { let touchstartY = 0 let lastTouchY = 0 + let immediateScrollParent: HTMLElement | undefined + let touchStartTopOffset = 0 const touchDiff = shallowRef(0) const scrollContainerRef = ref() - const canRefresh = computed(() => touchDiff.value > PULL_DOWN_HEIGHT_PX * 2 / 3) const refreshing = shallowRef(false) const touching = shallowRef(false) - let immediateScrollParent: HTMLElement | undefined + const canRefresh = shallowRef(false) + + const topOffset = computed(() => clamp(touchStartTopOffset + touchDiff.value, 0, PULL_DOWN_HEIGHT_PX)) function onTouchstart (e: TouchEvent) { - if (refreshing.value) { - e.preventDefault() - return - } touching.value = true lastTouchY = touchstartY = e.touches[0].clientY + immediateScrollParent!.scrollTop + touchStartTopOffset = topOffset.value } function onTouchmove (e: TouchEvent) { - if (refreshing.value) { - e.preventDefault() - return - } + if (canRefresh.value) return + const touchY = e.touches[0].clientY - if ( - touchY > lastTouchY && - touchDiff.value < PULL_DOWN_HEIGHT_PX && - !immediateScrollParent!.scrollTop - ) { + // Moving up to cancel existing loading + if (touchY < lastTouchY && touchstartY - touchY > PULL_DOWN_HEIGHT_PX / 3) { + refreshing.value = false + } + + if (!immediateScrollParent!.scrollTop) { touchDiff.value = touchY - touchstartY + canRefresh.value = touchDiff.value >= PULL_DOWN_HEIGHT_PX } lastTouchY = touchY } function onTouchend (e: TouchEvent) { - if (refreshing.value) { - e.preventDefault() - return - } touching.value = false if (canRefresh.value) { function done (status: PullToRefreshStatus) { + if (!refreshing.value) return if (status === 'ok') { touchDiff.value = 0 refreshing.value = false @@ -78,6 +75,7 @@ export const VPullToRefresh = genericComponent()({ } emit('load', { done }) refreshing.value = true + canRefresh.value = false } } @@ -103,12 +101,12 @@ export const VPullToRefresh = genericComponent()({ }, ]} style={{ - top: convertToUnit(-1 * PULL_DOWN_HEIGHT_PX + touchDiff.value), + top: convertToUnit(-1 * PULL_DOWN_HEIGHT_PX + topOffset.value), height: convertToUnit(PULL_DOWN_HEIGHT_PX), }} > { - canRefresh.value || refreshing.value ? ( + refreshing.value ? ( { slots.default?.() } From c312164a1c9ad7c9108e25861f5dddaf73b75b57 Mon Sep 17 00:00:00 2001 From: Yuchao Wu Date: Thu, 25 Apr 2024 14:49:40 +1000 Subject: [PATCH 04/16] chore: allow cancel before load, disallow cancel after load starts --- .../labs/VPullToRefresh/VPullToRefresh.tsx | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/packages/vuetify/src/labs/VPullToRefresh/VPullToRefresh.tsx b/packages/vuetify/src/labs/VPullToRefresh/VPullToRefresh.tsx index 07db4f6b652..a06d67e6543 100644 --- a/packages/vuetify/src/labs/VPullToRefresh/VPullToRefresh.tsx +++ b/packages/vuetify/src/labs/VPullToRefresh/VPullToRefresh.tsx @@ -9,7 +9,7 @@ import { VProgressCircular } from '@/components/VProgressCircular' import { LoaderSlot } from '@/composables/loader' // Utilities -import { computed, onMounted, ref, shallowRef } from 'vue' +import { computed, onMounted, ref, shallowRef, watchEffect } from 'vue' import { clamp, convertToUnit, genericComponent, getScrollParents, useRender } from '@/util' const PULL_DOWN_HEIGHT_PX = 64 @@ -28,9 +28,7 @@ export const VPullToRefresh = genericComponent()({ setup (props, { slots, emit }) { let touchstartY = 0 - let lastTouchY = 0 let immediateScrollParent: HTMLElement | undefined - let touchStartTopOffset = 0 const touchDiff = shallowRef(0) const scrollContainerRef = ref() @@ -39,31 +37,27 @@ export const VPullToRefresh = genericComponent()({ const touching = shallowRef(false) const canRefresh = shallowRef(false) - const topOffset = computed(() => clamp(touchStartTopOffset + touchDiff.value, 0, PULL_DOWN_HEIGHT_PX)) + const topOffset = computed(() => clamp(touchDiff.value, 0, PULL_DOWN_HEIGHT_PX)) function onTouchstart (e: TouchEvent) { + if (refreshing.value) return touching.value = true - lastTouchY = touchstartY = e.touches[0].clientY + immediateScrollParent!.scrollTop - touchStartTopOffset = topOffset.value + touchstartY = e.touches[0].clientY + immediateScrollParent!.scrollTop } function onTouchmove (e: TouchEvent) { - if (canRefresh.value) return + if (canRefresh.value || refreshing.value) return const touchY = e.touches[0].clientY - // Moving up to cancel existing loading - if (touchY < lastTouchY && touchstartY - touchY > PULL_DOWN_HEIGHT_PX / 3) { - refreshing.value = false - } if (!immediateScrollParent!.scrollTop) { touchDiff.value = touchY - touchstartY canRefresh.value = touchDiff.value >= PULL_DOWN_HEIGHT_PX } - lastTouchY = touchY } function onTouchend (e: TouchEvent) { + if (refreshing.value) return touching.value = false if (canRefresh.value) { function done (status: PullToRefreshStatus) { @@ -83,6 +77,12 @@ export const VPullToRefresh = genericComponent()({ immediateScrollParent = getScrollParents(scrollContainerRef.value)[0] }) + watchEffect(() => { + if (immediateScrollParent) { + immediateScrollParent.style.overflow = topOffset.value && !refreshing.value ? 'hidden' : 'auto' + } + }) + useRender(() => { return (
Date: Thu, 25 Apr 2024 18:44:20 +1000 Subject: [PATCH 05/16] chore: remove redundant logics --- .../labs/VPullToRefresh/VPullToRefresh.tsx | 31 ++++++++----------- 1 file changed, 13 insertions(+), 18 deletions(-) diff --git a/packages/vuetify/src/labs/VPullToRefresh/VPullToRefresh.tsx b/packages/vuetify/src/labs/VPullToRefresh/VPullToRefresh.tsx index a06d67e6543..6928bd245a5 100644 --- a/packages/vuetify/src/labs/VPullToRefresh/VPullToRefresh.tsx +++ b/packages/vuetify/src/labs/VPullToRefresh/VPullToRefresh.tsx @@ -9,13 +9,11 @@ import { VProgressCircular } from '@/components/VProgressCircular' import { LoaderSlot } from '@/composables/loader' // Utilities -import { computed, onMounted, ref, shallowRef, watchEffect } from 'vue' +import { computed, onMounted, ref, shallowRef, watch } from 'vue' import { clamp, convertToUnit, genericComponent, getScrollParents, useRender } from '@/util' const PULL_DOWN_HEIGHT_PX = 64 -export type PullToRefreshStatus = 'ok' | 'error' - export const VPullToRefresh = genericComponent()({ name: 'VPullToRefresh', @@ -23,7 +21,7 @@ export const VPullToRefresh = genericComponent()({ }, emits: { - load: (options: { done: (status: PullToRefreshStatus) => void }) => true, + load: (options: { done: () => void }) => true, }, setup (props, { slots, emit }) { @@ -31,11 +29,10 @@ export const VPullToRefresh = genericComponent()({ let immediateScrollParent: HTMLElement | undefined const touchDiff = shallowRef(0) - const scrollContainerRef = ref() + const containerRef = ref() const refreshing = shallowRef(false) const touching = shallowRef(false) - const canRefresh = shallowRef(false) const topOffset = computed(() => clamp(touchDiff.value, 0, PULL_DOWN_HEIGHT_PX)) @@ -46,38 +43,36 @@ export const VPullToRefresh = genericComponent()({ } function onTouchmove (e: TouchEvent) { - if (canRefresh.value || refreshing.value) return + if (refreshing.value) return const touchY = e.touches[0].clientY if (!immediateScrollParent!.scrollTop) { touchDiff.value = touchY - touchstartY - canRefresh.value = touchDiff.value >= PULL_DOWN_HEIGHT_PX } } function onTouchend (e: TouchEvent) { if (refreshing.value) return touching.value = false - if (canRefresh.value) { - function done (status: PullToRefreshStatus) { + if (touchDiff.value >= PULL_DOWN_HEIGHT_PX) { + function done () { if (!refreshing.value) return - if (status === 'ok') { - touchDiff.value = 0 - refreshing.value = false - } + touchDiff.value = 0 + refreshing.value = false } emit('load', { done }) refreshing.value = true - canRefresh.value = false + } else { + touchDiff.value = 0 } } onMounted(() => { - immediateScrollParent = getScrollParents(scrollContainerRef.value)[0] + immediateScrollParent = getScrollParents(containerRef.value)[0] }) - watchEffect(() => { + watch([topOffset, refreshing], () => { if (immediateScrollParent) { immediateScrollParent.style.overflow = topOffset.value && !refreshing.value ? 'hidden' : 'auto' } @@ -92,6 +87,7 @@ export const VPullToRefresh = genericComponent()({ onTouchstart={ onTouchstart } onTouchmove={ onTouchmove } onTouchend={ onTouchend } + ref={ containerRef } >
{ slots.default?.() } From 9fad85ee2712d0065c63f9b2320f40d531eecdc5 Mon Sep 17 00:00:00 2001 From: Yuchao Wu Date: Fri, 26 Apr 2024 20:45:57 +1000 Subject: [PATCH 06/16] chore: support web mouse events --- .../labs/VPullToRefresh/VPullToRefresh.tsx | 35 +++++++++++-------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/packages/vuetify/src/labs/VPullToRefresh/VPullToRefresh.tsx b/packages/vuetify/src/labs/VPullToRefresh/VPullToRefresh.tsx index 6928bd245a5..53eb2a0e69b 100644 --- a/packages/vuetify/src/labs/VPullToRefresh/VPullToRefresh.tsx +++ b/packages/vuetify/src/labs/VPullToRefresh/VPullToRefresh.tsx @@ -12,12 +12,14 @@ import { LoaderSlot } from '@/composables/loader' import { computed, onMounted, ref, shallowRef, watch } from 'vue' import { clamp, convertToUnit, genericComponent, getScrollParents, useRender } from '@/util' -const PULL_DOWN_HEIGHT_PX = 64 - export const VPullToRefresh = genericComponent()({ name: 'VPullToRefresh', props: { + loadThreshold: { + type: Number, + default: 64, + }, }, emits: { @@ -34,28 +36,29 @@ export const VPullToRefresh = genericComponent()({ const refreshing = shallowRef(false) const touching = shallowRef(false) - const topOffset = computed(() => clamp(touchDiff.value, 0, PULL_DOWN_HEIGHT_PX)) + const canRefresh = computed(() => touchDiff.value >= props.loadThreshold) + const topOffset = computed(() => clamp(touchDiff.value, 0, props.loadThreshold)) - function onTouchstart (e: TouchEvent) { + function onTouchstart (e: TouchEvent | MouseEvent) { if (refreshing.value) return touching.value = true - touchstartY = e.touches[0].clientY + immediateScrollParent!.scrollTop + touchstartY = ('clientY' in e ? e.clientY : e.touches[0].clientY) + immediateScrollParent!.scrollTop } - function onTouchmove (e: TouchEvent) { - if (refreshing.value) return + function onTouchmove (e: TouchEvent | MouseEvent) { + if (refreshing.value || !touching.value) return - const touchY = e.touches[0].clientY + const touchY = 'clientY' in e ? e.clientY : e.touches[0].clientY - if (!immediateScrollParent!.scrollTop) { + if (!immediateScrollParent!.scrollTop || e instanceof MouseEvent) { touchDiff.value = touchY - touchstartY } } - function onTouchend (e: TouchEvent) { + function onTouchend (e: TouchEvent | MouseEvent) { if (refreshing.value) return touching.value = false - if (touchDiff.value >= PULL_DOWN_HEIGHT_PX) { + if (canRefresh.value) { function done () { if (!refreshing.value) return touchDiff.value = 0 @@ -87,6 +90,10 @@ export const VPullToRefresh = genericComponent()({ onTouchstart={ onTouchstart } onTouchmove={ onTouchmove } onTouchend={ onTouchend } + onMousedown={ onTouchstart } + onMouseup={ onTouchend } + onMouseleave={ onTouchend } + onMousemove={ onTouchmove } ref={ containerRef } >
{ @@ -114,7 +121,7 @@ export const VPullToRefresh = genericComponent()({ ) : ( ) } From 9950b453809b9fc8ca2c68e93c81663a14f5c825 Mon Sep 17 00:00:00 2001 From: Yuchao Wu Date: Fri, 26 Apr 2024 21:38:28 +1000 Subject: [PATCH 07/16] chore: introduce two props --- .../labs/VPullToRefresh/VPullToRefresh.tsx | 25 +++++++++++++------ 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/packages/vuetify/src/labs/VPullToRefresh/VPullToRefresh.tsx b/packages/vuetify/src/labs/VPullToRefresh/VPullToRefresh.tsx index 53eb2a0e69b..72aff97aec5 100644 --- a/packages/vuetify/src/labs/VPullToRefresh/VPullToRefresh.tsx +++ b/packages/vuetify/src/labs/VPullToRefresh/VPullToRefresh.tsx @@ -16,10 +16,14 @@ export const VPullToRefresh = genericComponent()({ name: 'VPullToRefresh', props: { - loadThreshold: { + pullDownThreshold: { type: Number, default: 64, }, + enablePullOnScrollTop: { + type: Number, + default: 0, + }, }, emits: { @@ -34,15 +38,16 @@ export const VPullToRefresh = genericComponent()({ const containerRef = ref() const refreshing = shallowRef(false) + const goingUp = shallowRef(false) const touching = shallowRef(false) - const canRefresh = computed(() => touchDiff.value >= props.loadThreshold) - const topOffset = computed(() => clamp(touchDiff.value, 0, props.loadThreshold)) + const canRefresh = computed(() => touchDiff.value >= props.pullDownThreshold) + const topOffset = computed(() => clamp(touchDiff.value, 0, props.pullDownThreshold)) function onTouchstart (e: TouchEvent | MouseEvent) { if (refreshing.value) return touching.value = true - touchstartY = ('clientY' in e ? e.clientY : e.touches[0].clientY) + immediateScrollParent!.scrollTop + touchstartY = 'clientY' in e ? e.clientY : e.touches[0].clientY } function onTouchmove (e: TouchEvent | MouseEvent) { @@ -50,7 +55,7 @@ export const VPullToRefresh = genericComponent()({ const touchY = 'clientY' in e ? e.clientY : e.touches[0].clientY - if (!immediateScrollParent!.scrollTop || e instanceof MouseEvent) { + if (immediateScrollParent!.scrollTop >= props.enablePullOnScrollTop) { touchDiff.value = touchY - touchstartY } } @@ -81,6 +86,10 @@ export const VPullToRefresh = genericComponent()({ } }) + watch(topOffset, (newVal, oldVal) => { + goingUp.value = newVal < oldVal + }) + useRender(() => { return (
{ @@ -121,7 +130,7 @@ export const VPullToRefresh = genericComponent()({ ) : ( ) } From 3dda536f32a57283ef307e4a44d7166ffc083233 Mon Sep 17 00:00:00 2001 From: Yuchao Wu Date: Fri, 26 Apr 2024 22:06:09 +1000 Subject: [PATCH 08/16] chore: add pullDownPanel slot --- .../labs/VPullToRefresh/VPullToRefresh.tsx | 37 +++++++++++-------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/packages/vuetify/src/labs/VPullToRefresh/VPullToRefresh.tsx b/packages/vuetify/src/labs/VPullToRefresh/VPullToRefresh.tsx index 72aff97aec5..95b71799ed1 100644 --- a/packages/vuetify/src/labs/VPullToRefresh/VPullToRefresh.tsx +++ b/packages/vuetify/src/labs/VPullToRefresh/VPullToRefresh.tsx @@ -5,14 +5,20 @@ import './VPullToRefresh.sass' import { VIcon } from '@/components/VIcon' import { VProgressCircular } from '@/components/VProgressCircular' -// Composables -import { LoaderSlot } from '@/composables/loader' - // Utilities import { computed, onMounted, ref, shallowRef, watch } from 'vue' import { clamp, convertToUnit, genericComponent, getScrollParents, useRender } from '@/util' -export const VPullToRefresh = genericComponent()({ +export type VPullToRefreshSlots = { + default: never + pullDownPanel: { + canRefresh: boolean + goingUp: boolean + refreshing: boolean + } +} + +export const VPullToRefresh = genericComponent()({ name: 'VPullToRefresh', props: { @@ -41,7 +47,7 @@ export const VPullToRefresh = genericComponent()({ const goingUp = shallowRef(false) const touching = shallowRef(false) - const canRefresh = computed(() => touchDiff.value >= props.pullDownThreshold) + const canRefresh = computed(() => touchDiff.value >= props.pullDownThreshold && !refreshing.value) const topOffset = computed(() => clamp(touchDiff.value, 0, props.pullDownThreshold)) function onTouchstart (e: TouchEvent | MouseEvent) { @@ -117,22 +123,21 @@ export const VPullToRefresh = genericComponent()({ height: convertToUnit(props.pullDownThreshold), }} > - { - refreshing.value ? ( - - - + /> ) : ( - ) + )) }
Date: Fri, 26 Apr 2024 22:46:35 +1000 Subject: [PATCH 09/16] chore: remove enablePullOnScrollTop prop --- packages/vuetify/src/labs/VPullToRefresh/VPullToRefresh.tsx | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/packages/vuetify/src/labs/VPullToRefresh/VPullToRefresh.tsx b/packages/vuetify/src/labs/VPullToRefresh/VPullToRefresh.tsx index 95b71799ed1..018c910c6d6 100644 --- a/packages/vuetify/src/labs/VPullToRefresh/VPullToRefresh.tsx +++ b/packages/vuetify/src/labs/VPullToRefresh/VPullToRefresh.tsx @@ -26,10 +26,6 @@ export const VPullToRefresh = genericComponent()({ type: Number, default: 64, }, - enablePullOnScrollTop: { - type: Number, - default: 0, - }, }, emits: { @@ -61,7 +57,7 @@ export const VPullToRefresh = genericComponent()({ const touchY = 'clientY' in e ? e.clientY : e.touches[0].clientY - if (immediateScrollParent!.scrollTop >= props.enablePullOnScrollTop) { + if (!immediateScrollParent!.scrollTop) { touchDiff.value = touchY - touchstartY } } From 368ae8d62753fc56b9d3f1bd38e4a74c9f7d726b Mon Sep 17 00:00:00 2001 From: Yuchao Wu Date: Sat, 27 Apr 2024 10:33:56 +1000 Subject: [PATCH 10/16] stop all scrollable parents scrolling when pulling down --- .../src/labs/VPullToRefresh/VPullToRefresh.tsx | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/packages/vuetify/src/labs/VPullToRefresh/VPullToRefresh.tsx b/packages/vuetify/src/labs/VPullToRefresh/VPullToRefresh.tsx index 018c910c6d6..0a0cf682026 100644 --- a/packages/vuetify/src/labs/VPullToRefresh/VPullToRefresh.tsx +++ b/packages/vuetify/src/labs/VPullToRefresh/VPullToRefresh.tsx @@ -34,7 +34,7 @@ export const VPullToRefresh = genericComponent()({ setup (props, { slots, emit }) { let touchstartY = 0 - let immediateScrollParent: HTMLElement | undefined + let immediateScrollParents: HTMLElement[] = [] const touchDiff = shallowRef(0) const containerRef = ref() @@ -57,7 +57,7 @@ export const VPullToRefresh = genericComponent()({ const touchY = 'clientY' in e ? e.clientY : e.touches[0].clientY - if (!immediateScrollParent!.scrollTop) { + if (immediateScrollParents.length && !immediateScrollParents[0].scrollTop) { touchDiff.value = touchY - touchstartY } } @@ -79,12 +79,13 @@ export const VPullToRefresh = genericComponent()({ } onMounted(() => { - immediateScrollParent = getScrollParents(containerRef.value)[0] + immediateScrollParents = getScrollParents(containerRef.value) }) watch([topOffset, refreshing], () => { - if (immediateScrollParent) { - immediateScrollParent.style.overflow = topOffset.value && !refreshing.value ? 'hidden' : 'auto' + if (immediateScrollParents.length) { + const stopScrolling = topOffset.value && !refreshing.value + immediateScrollParents.forEach(p => p.style.overflow = stopScrolling ? 'hidden' : 'auto') } }) From ace3c1cc07b26c508ac5f01dba2e02cf013c9282 Mon Sep 17 00:00:00 2001 From: Yuchao Wu Date: Sat, 27 Apr 2024 10:51:01 +1000 Subject: [PATCH 11/16] move default pull down panel to a dedicated div --- .../labs/VPullToRefresh/VPullToRefresh.sass | 10 +++++-- .../labs/VPullToRefresh/VPullToRefresh.tsx | 30 ++++++++++++------- 2 files changed, 27 insertions(+), 13 deletions(-) diff --git a/packages/vuetify/src/labs/VPullToRefresh/VPullToRefresh.sass b/packages/vuetify/src/labs/VPullToRefresh/VPullToRefresh.sass index d10655bb273..e8befecf93b 100644 --- a/packages/vuetify/src/labs/VPullToRefresh/VPullToRefresh.sass +++ b/packages/vuetify/src/labs/VPullToRefresh/VPullToRefresh.sass @@ -4,13 +4,17 @@ &__pull-down position: absolute width: 100% - display: flex - justify-content: center - align-items: flex-end transition: top .3s ease-out &--touching transition: none + &__pull-down-default + display: flex + width: 100% + height: 100% + justify-content: center + align-items: flex-end + padding-bottom: 10px &__scroll-container position: relative diff --git a/packages/vuetify/src/labs/VPullToRefresh/VPullToRefresh.tsx b/packages/vuetify/src/labs/VPullToRefresh/VPullToRefresh.tsx index 0a0cf682026..646327223b6 100644 --- a/packages/vuetify/src/labs/VPullToRefresh/VPullToRefresh.tsx +++ b/packages/vuetify/src/labs/VPullToRefresh/VPullToRefresh.tsx @@ -125,16 +125,26 @@ export const VPullToRefresh = genericComponent()({ canRefresh: canRefresh.value, goingUp: goingUp.value, refreshing: refreshing.value, - }) : (refreshing.value ? ( - - ) : ( - - )) + }) : ( +
+ { + refreshing.value ? ( + + ) : ( + + ) + } +
+ ) }
Date: Sat, 27 Apr 2024 10:56:18 +1000 Subject: [PATCH 12/16] finish doc --- packages/docs/src/data/nav.json | 4 ++ .../src/examples/v-pull-to-refresh/usage.vue | 56 +++++++++++++++++++ .../pages/en/components/pull-to-refresh.md | 54 ++++++++++++++++++ 3 files changed, 114 insertions(+) create mode 100644 packages/docs/src/examples/v-pull-to-refresh/usage.vue create mode 100644 packages/docs/src/pages/en/components/pull-to-refresh.md diff --git a/packages/docs/src/data/nav.json b/packages/docs/src/data/nav.json index 097e219856a..aa1f43daf68 100644 --- a/packages/docs/src/data/nav.json +++ b/packages/docs/src/data/nav.json @@ -243,6 +243,10 @@ "title": "number-inputs", "subfolder": "components" }, + { + "title": "pull-to-refresh", + "subfolder": "components" + }, { "title": "snackbar-queue", "subfolder": "components" diff --git a/packages/docs/src/examples/v-pull-to-refresh/usage.vue b/packages/docs/src/examples/v-pull-to-refresh/usage.vue new file mode 100644 index 00000000000..e16554a2942 --- /dev/null +++ b/packages/docs/src/examples/v-pull-to-refresh/usage.vue @@ -0,0 +1,56 @@ + + + + diff --git a/packages/docs/src/pages/en/components/pull-to-refresh.md b/packages/docs/src/pages/en/components/pull-to-refresh.md new file mode 100644 index 00000000000..5207bb51279 --- /dev/null +++ b/packages/docs/src/pages/en/components/pull-to-refresh.md @@ -0,0 +1,54 @@ +--- +emphasized: true +meta: + title: Pull To Refresh + description: The PullToRefresh allows users to update content with a simple downward swipe on their screen. + keywords: Pull to refresh, vuetify Pull to refresh component, vue pull to refresh component +features: + label: 'C: VPullToRefresh' + github: /components/VPullToRefresh/ + report: true +--- + +# Pull To Refresh + +The PullToRefresh allows users to update content with a simple downward swipe on their screen. Works for Mobile and Desktop. **It allows pulling down as soon as its immediate scrollable parent has scrolled to the top**. + + + +::: warning + +This feature requires [v3.6.0](/getting-started/release-notes/?version=v3.6.0) + +::: + +## Installation + +Labs components require a manual import and installation of the component. + +```js { resource="src/plugins/vuetify.js" } +import { VPullToRefresh } from 'vuetify/labs/VPullToRefresh' + +export default createVuetify({ + components: { + VPullToRefresh, + }, +}) +``` + +## Usage + +Drag the list downward to activate the pull-to-refresh feature. + + + + + + +## API + +| Component | Description | +| - | - | +| [v-pull-to-refresh](/api/v-pull-to-refresh/) | Primary Component | + + From 46c7bfdb2ccd68bda97cf2c0b0f9771a655f470b Mon Sep 17 00:00:00 2001 From: Yuchao Wu Date: Sat, 27 Apr 2024 11:01:40 +1000 Subject: [PATCH 13/16] improve naming --- .../src/labs/VPullToRefresh/VPullToRefresh.sass | 2 +- .../vuetify/src/labs/VPullToRefresh/VPullToRefresh.tsx | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/vuetify/src/labs/VPullToRefresh/VPullToRefresh.sass b/packages/vuetify/src/labs/VPullToRefresh/VPullToRefresh.sass index e8befecf93b..7b3d28886ab 100644 --- a/packages/vuetify/src/labs/VPullToRefresh/VPullToRefresh.sass +++ b/packages/vuetify/src/labs/VPullToRefresh/VPullToRefresh.sass @@ -20,4 +20,4 @@ position: relative transition: top .3s ease-out &--touching - transition: none \ No newline at end of file + transition: none diff --git a/packages/vuetify/src/labs/VPullToRefresh/VPullToRefresh.tsx b/packages/vuetify/src/labs/VPullToRefresh/VPullToRefresh.tsx index 646327223b6..0d85b0a8908 100644 --- a/packages/vuetify/src/labs/VPullToRefresh/VPullToRefresh.tsx +++ b/packages/vuetify/src/labs/VPullToRefresh/VPullToRefresh.tsx @@ -34,7 +34,7 @@ export const VPullToRefresh = genericComponent()({ setup (props, { slots, emit }) { let touchstartY = 0 - let immediateScrollParents: HTMLElement[] = [] + let scrollParents: HTMLElement[] = [] const touchDiff = shallowRef(0) const containerRef = ref() @@ -57,7 +57,7 @@ export const VPullToRefresh = genericComponent()({ const touchY = 'clientY' in e ? e.clientY : e.touches[0].clientY - if (immediateScrollParents.length && !immediateScrollParents[0].scrollTop) { + if (scrollParents.length && !scrollParents[0].scrollTop) { touchDiff.value = touchY - touchstartY } } @@ -79,13 +79,13 @@ export const VPullToRefresh = genericComponent()({ } onMounted(() => { - immediateScrollParents = getScrollParents(containerRef.value) + scrollParents = getScrollParents(containerRef.value) }) watch([topOffset, refreshing], () => { - if (immediateScrollParents.length) { + if (scrollParents.length) { const stopScrolling = topOffset.value && !refreshing.value - immediateScrollParents.forEach(p => p.style.overflow = stopScrolling ? 'hidden' : 'auto') + scrollParents.forEach(p => p.style.overflow = stopScrolling ? 'hidden' : 'auto') } }) From 6f4e667891786314d9903d1241624f145fe4c431 Mon Sep 17 00:00:00 2001 From: Yuchao Wu Date: Sat, 27 Apr 2024 11:08:35 +1000 Subject: [PATCH 14/16] fix lint error --- packages/docs/src/pages/en/components/pull-to-refresh.md | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/docs/src/pages/en/components/pull-to-refresh.md b/packages/docs/src/pages/en/components/pull-to-refresh.md index 5207bb51279..bf1c6478308 100644 --- a/packages/docs/src/pages/en/components/pull-to-refresh.md +++ b/packages/docs/src/pages/en/components/pull-to-refresh.md @@ -40,7 +40,6 @@ export default createVuetify({ Drag the list downward to activate the pull-to-refresh feature. - From 6a6d0deb52d4862739459353a4dc2eaa51717ae2 Mon Sep 17 00:00:00 2001 From: John Leider Date: Mon, 29 Apr 2024 12:16:37 -0500 Subject: [PATCH 15/16] docs(VPullToRefresh): update content --- .../docs/src/pages/en/components/pull-to-refresh.md | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/packages/docs/src/pages/en/components/pull-to-refresh.md b/packages/docs/src/pages/en/components/pull-to-refresh.md index bf1c6478308..1b762526753 100644 --- a/packages/docs/src/pages/en/components/pull-to-refresh.md +++ b/packages/docs/src/pages/en/components/pull-to-refresh.md @@ -12,9 +12,9 @@ features: # Pull To Refresh -The PullToRefresh allows users to update content with a simple downward swipe on their screen. Works for Mobile and Desktop. **It allows pulling down as soon as its immediate scrollable parent has scrolled to the top**. +The PullToRefresh allows users to update content with a simple downward swipe on their screen. Works for Mobile and Desktop. - + ::: warning @@ -42,6 +42,12 @@ Drag the list downward to activate the pull-to-refresh feature. +::: tip + +Pull down functionality is available as soon as its immediate scrollable parent has scrolled to the top. + +::: + ## API From 57175b537aac83550e4b3ce87ab9a46d78048375 Mon Sep 17 00:00:00 2001 From: John Leider Date: Mon, 29 Apr 2024 12:37:17 -0500 Subject: [PATCH 16/16] docs(VPullToRefresh): fix usage --- .../src/examples/v-pull-to-refresh/usage.vue | 42 +++++++++++++++++-- 1 file changed, 39 insertions(+), 3 deletions(-) diff --git a/packages/docs/src/examples/v-pull-to-refresh/usage.vue b/packages/docs/src/examples/v-pull-to-refresh/usage.vue index e16554a2942..9ac2ee24d71 100644 --- a/packages/docs/src/examples/v-pull-to-refresh/usage.vue +++ b/packages/docs/src/examples/v-pull-to-refresh/usage.vue @@ -1,5 +1,5 @@