From 8fcf97d0b62483494148b5c738dc3ff7f86ed7eb Mon Sep 17 00:00:00 2001 From: Brandon Reid Date: Fri, 18 Aug 2023 16:10:08 -0300 Subject: [PATCH 1/3] add usePositionStickyObserver --- src/index.ts | 1 + src/usePositionStickyObserver/README.md | 34 +++++++++++ src/usePositionStickyObserver/index.ts | 1 + .../usePositionStickyObserver.ts | 57 +++++++++++++++++++ 4 files changed, 93 insertions(+) create mode 100644 src/usePositionStickyObserver/README.md create mode 100644 src/usePositionStickyObserver/index.ts create mode 100644 src/usePositionStickyObserver/usePositionStickyObserver.ts diff --git a/src/index.ts b/src/index.ts index 4ddf5d5d..7af693a7 100644 --- a/src/index.ts +++ b/src/index.ts @@ -13,6 +13,7 @@ export * from './useMutationObserver' export * from './useNow' export * from './usePatchRef' export * from './useResizeObserver' +export * from './usePositionStickyObserver' export * from './useRouteParam' export * from './useRouteQuery' export * from './useRouteQueryParam' diff --git a/src/usePositionStickyObserver/README.md b/src/usePositionStickyObserver/README.md new file mode 100644 index 00000000..1726b3db --- /dev/null +++ b/src/usePositionStickyObserver/README.md @@ -0,0 +1,34 @@ +# usePositionStickyObserver + +This composition is abstracts away the logic necessary to determine if a `position: sticky;` element has gone into it's "stuck" mode. This is useful when you want to style the element differently when it's stuck, like if you want to add a background color. + +## Example + +```typescript +const stickyHeader = ref() +const { stuck } = usePositionStickyObserver(stickyHeader) + +const classes = computed(() ({ + header: { + 'header--stuck': stuck.value, + } +})) +``` + +## Arguments + +| Name | Type | +| ------- | ---------------------------------------------- | +| element | `HTMLElement \| Ref` | +| options | '{}' | + +### Options + +| Name | Type | +| --------------- | ------------------------------------------------------------------------------------------------------ | +| rootMargin | `string`, [MDN DOcs](https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserver/rootMargin) | +| boundingElement | `HTMLElement \| Ref`. The scroll container, defaults to the body. | + +## Returns + +`UsePositionStickyObserverResponse` diff --git a/src/usePositionStickyObserver/index.ts b/src/usePositionStickyObserver/index.ts new file mode 100644 index 00000000..3e7fe724 --- /dev/null +++ b/src/usePositionStickyObserver/index.ts @@ -0,0 +1 @@ +export * from './usePositionStickyObserver' \ No newline at end of file diff --git a/src/usePositionStickyObserver/usePositionStickyObserver.ts b/src/usePositionStickyObserver/usePositionStickyObserver.ts new file mode 100644 index 00000000..e942dcc7 --- /dev/null +++ b/src/usePositionStickyObserver/usePositionStickyObserver.ts @@ -0,0 +1,57 @@ +import { Ref, computed, onMounted, ref, toValue } from 'vue' +import { MaybeRefOrGetter } from '@/types/maybe' +import { useIntersectionObserver } from '@/useIntersectionObserver' + +export type UsePositionStickyObserverResponse = { + stuck: Ref, +} + +export type UsePositionStickyObserverOptions = { + rootMargin?: string, + boundingElement?: MaybeRefOrGetter, +} + +const usePositionStickyObserverDefaultOptions = { + rootMargin: '-1px 0px 0px 0px', + boundingElement: document.body, +} + +export function usePositionStickyObserver( + element: MaybeRefOrGetter, + options?: MaybeRefOrGetter, +): UsePositionStickyObserverResponse { + const elementRef = computed(() => toValue(element)) + const optionsRef = computed(() => { + if (!options) { + return usePositionStickyObserverDefaultOptions + } + + const { rootMargin, boundingElement } = toValue(options) + const boundingElementRef = toValue(boundingElement) + + return { + rootMargin: rootMargin ?? usePositionStickyObserverDefaultOptions.rootMargin, + boundingElement: boundingElementRef ?? usePositionStickyObserverDefaultOptions.boundingElement, + } + }) + + const stuck = ref(false) + + function intersect(entries: IntersectionObserverEntry[]): void { + entries.forEach(entry => { + stuck.value = entry.intersectionRatio < 1 + }) + } + + const { observe } = useIntersectionObserver(intersect, computed(() => ({ + threshold: [1], + rootMargin: optionsRef.value.rootMargin, + root: optionsRef.value.boundingElement, + }))) + + onMounted(() => observe(elementRef)) + + return { + stuck, + } +} \ No newline at end of file From 538f4780a0b5f74e1b4d186c934c6862120d2310 Mon Sep 17 00:00:00 2001 From: Brandon Reid Date: Mon, 21 Aug 2023 15:33:14 -0300 Subject: [PATCH 2/3] import toValue from utilities --- src/usePositionStickyObserver/usePositionStickyObserver.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/usePositionStickyObserver/usePositionStickyObserver.ts b/src/usePositionStickyObserver/usePositionStickyObserver.ts index e942dcc7..c429a89a 100644 --- a/src/usePositionStickyObserver/usePositionStickyObserver.ts +++ b/src/usePositionStickyObserver/usePositionStickyObserver.ts @@ -1,6 +1,7 @@ -import { Ref, computed, onMounted, ref, toValue } from 'vue' +import { Ref, computed, onMounted, ref } from 'vue' import { MaybeRefOrGetter } from '@/types/maybe' import { useIntersectionObserver } from '@/useIntersectionObserver' +import { toValue } from '@/utilities/vue' export type UsePositionStickyObserverResponse = { stuck: Ref, From 2cf6a8988ae67437f6217868617942167d934fd9 Mon Sep 17 00:00:00 2001 From: Brandon Reid Date: Wed, 23 Aug 2023 13:23:14 -0300 Subject: [PATCH 3/3] cleanup per PR comments --- .../usePositionStickyObserver.ts | 26 +++++++------------ 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/src/usePositionStickyObserver/usePositionStickyObserver.ts b/src/usePositionStickyObserver/usePositionStickyObserver.ts index c429a89a..c1df2c51 100644 --- a/src/usePositionStickyObserver/usePositionStickyObserver.ts +++ b/src/usePositionStickyObserver/usePositionStickyObserver.ts @@ -9,7 +9,7 @@ export type UsePositionStickyObserverResponse = { export type UsePositionStickyObserverOptions = { rootMargin?: string, - boundingElement?: MaybeRefOrGetter, + boundingElement?: HTMLElement, } const usePositionStickyObserverDefaultOptions = { @@ -22,33 +22,27 @@ export function usePositionStickyObserver( options?: MaybeRefOrGetter, ): UsePositionStickyObserverResponse { const elementRef = computed(() => toValue(element)) - const optionsRef = computed(() => { - if (!options) { - return usePositionStickyObserverDefaultOptions - } + const stuck = ref(false) - const { rootMargin, boundingElement } = toValue(options) - const boundingElementRef = toValue(boundingElement) + const observerOptions = computed(() => { + const { rootMargin: rootMarginOption, boundingElement: boundingElementOption } = toValue(options ?? {}) + const rootMargin = rootMarginOption ?? usePositionStickyObserverDefaultOptions.rootMargin + const root = boundingElementOption ?? usePositionStickyObserverDefaultOptions.boundingElement return { - rootMargin: rootMargin ?? usePositionStickyObserverDefaultOptions.rootMargin, - boundingElement: boundingElementRef ?? usePositionStickyObserverDefaultOptions.boundingElement, + threshold: [1], + rootMargin, + root, } }) - const stuck = ref(false) - function intersect(entries: IntersectionObserverEntry[]): void { entries.forEach(entry => { stuck.value = entry.intersectionRatio < 1 }) } - const { observe } = useIntersectionObserver(intersect, computed(() => ({ - threshold: [1], - rootMargin: optionsRef.value.rootMargin, - root: optionsRef.value.boundingElement, - }))) + const { observe } = useIntersectionObserver(intersect, observerOptions) onMounted(() => observe(elementRef))