Skip to content

Commit

Permalink
1.4.3 (#31)
Browse files Browse the repository at this point in the history
* Core - Improve perf when sorting elements, add debounce timeout id to global store

* NotivueSwipe, Notivue - Implement touch debounce timeout

* Core, CSS - Format

* Tests - Improve pause-on-touch test readability

* NotivueSwipe, Tests - Add debouce tests

* Pkg - Edit readme, demo

* Core - Call window.setTimeout instead of setTimeout to improve IDE hints

* Core - Move debounce timeout setter to global store

* Tests - Fix checkAnimations test flakiness

* NotivueSwipe, Tests - Add proper debounce tests, move to different file

* Pkg - Up deps

* NotivueSwipe - Rename debounce store props

* NotivueSwipe - Fix isPointerInside computation

* Core - Prevent lightningcss keyframe transform

* Demo - Re-enable lightningcss minification

* NotivueSwipe - Remove some debug statements

* NotivueSwipe - Improve readab last item, increase default debounce timings

* NotivueSwipe, Tests - Fine tuned debounce tests

* Pkg - Edit README

* Pkg - Bump 1.4.3

* Core - Rename `setDebounceTimeout` to `resumeWithDebounce`

* Core - Improve some comments
  • Loading branch information
smastrom committed Nov 1, 2023
1 parent 00ab0cd commit 9c0f803
Show file tree
Hide file tree
Showing 22 changed files with 297 additions and 143 deletions.
15 changes: 8 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,18 +29,18 @@ _Granularly include only the features you need_
**💊 Drop-in components to enhance notifications**
_NotivueSwipe, NotivueKeyboard, all optional and customizable_

**🎢 Slick transitions and animations**
_Customize any animation with CSS_

**🧩 Custom Components API**
_Use your own components while Notivue handles the rest_
**🧩 Headless API**
_Use your own notifications while Notivue handles the rest_

**🌀 Promise API**
_Update pending notifications with ease_

**🔰 Includes a ready-made component with anything you need**
_Themes, icons, rtl support and much more_

**🎢 Slick transitions and animations**
_Customize any animation with plain CSS_

**♿️ Fully accessible**
_Built-in screen reader, reduced motion, and keyboard support_

Expand All @@ -61,7 +61,7 @@ pnpm add notivue

<br />

## Single-page app (Vite or Webpack)
## Single-page app (Vite)

> :bulb: See [↓ below](#nuxt) for **Nuxt**
Expand Down Expand Up @@ -103,7 +103,7 @@ import { push } from './main'
```

<details>
<summary><strong>Using custom components</strong></summary>
<summary><strong>Headless, with custom components</strong></summary>

```vue
<script setup>
Expand All @@ -115,6 +115,7 @@ import { push } from './main'
<button @click="push.success('This is your first notification!')">Push</button>
<Notivue v-slot="item">
<!-- Your custom notification -->
<div class="rounded-full flex py-2 pl-3 bg-slate-700 text-slate-50 text-sm">
<p :role="item.ariaRole" :aria-live="item.ariaLive">
{{ item.message }}
Expand Down
6 changes: 3 additions & 3 deletions demo/components/custom-notifications/CustomPromise.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ import CloseIcon from '../icons/CloseIcon.vue'
import type { NotivueItem } from 'notivue'
import type { CustomPromiseProps } from '../nav/NavPushCustom.vue'
const props = defineProps<{
const notification = defineProps<{
item: NotivueItem<CustomPromiseProps>
}>()
const isPromise = computed(() => props.item.type === 'promise')
const isPromise = computed(() => notification.item.type === 'promise')
</script>

<template>
Expand Down Expand Up @@ -36,7 +36,7 @@ const isPromise = computed(() => props.item.type === 'promise')
</div>

<div class="Footer" v-else>
<time class="Time">{{ toNow(props.item.createdAt) }} ago</time>
<time class="Time">{{ toNow(item.createdAt) }} ago</time>
<p><strong>Remaining space:</strong> 302.1 GB</p>
</div>
</div>
Expand Down
8 changes: 4 additions & 4 deletions demo/components/custom-notifications/CustomSimple.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,18 @@
import type { NotivueItem } from 'notivue'
import type { CustomSimpleProps } from '../nav/NavPushCustom.vue'
const props = defineProps<{
defineProps<{
item: NotivueItem<CustomSimpleProps>
}>()
</script>

<template>
<div class="Notification">
<p :role="props.item.ariaRole" :aria-live="props.item.ariaLive">
{{ props.item.message }}
<p :role="item.ariaRole" :aria-live="item.ariaLive">
{{ item.message }}
</p>

<button @click="props.item.clear" aria-label="Dismiss" tabindex="-1" class="CloseButton">
<button @click="item.clear" aria-label="Dismiss" tabindex="-1" class="CloseButton">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
Expand Down
1 change: 0 additions & 1 deletion demo/nuxt.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ export default defineNuxtConfig({
},
notivue: {
// addPlugin: true,
// limit: Infinity,
notifications: {
global: {
// duration: Infinity,
Expand Down
4 changes: 2 additions & 2 deletions demo/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@
"devDependencies": {
"@nuxt/devtools": "1.0.0-beta.3",
"@types/luxon": "^3.3.3",
"@types/node": "^20.8.7",
"@types/node": "^20.8.10",
"lightningcss": "^1.22.0",
"nuxt": "^3.7.4"
"nuxt": "^3.8.0"
},
"dependencies": {
"floating-vue": "2.0.0-beta.24",
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@
"prepare": "husky install"
},
"devDependencies": {
"concurrently": "^8.2.1",
"concurrently": "^8.2.2",
"husky": "^8.0.3",
"lint-staged": "^15.0.1",
"lint-staged": "^15.0.2",
"prettier": "^3.0.3"
},
"lint-staged": {
Expand Down
3 changes: 2 additions & 1 deletion packages/notivue/Notifications/notifications.css
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,8 @@
border-radius: var(--nv-radius, 0);
border: var(--nv-border-width, 0) solid var(--nv-border, var(--nv-global-border));

box-shadow: var(--nv-shadow, 0 0 rgba(0, 0, 0, 0)),
box-shadow:
var(--nv-shadow, 0 0 rgba(0, 0, 0, 0)),
inset var(--tip-width) 0 0 var(--nv-accent, var(--nv-global-accent));

& * {
Expand Down
12 changes: 4 additions & 8 deletions packages/notivue/Notivue/composables/useTouchEvents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { isMouse } from '@/core/utils'
/**
* The logic follows this pattern:
*
* Every time users touch the stream, all notifications
* Every time users tap the stream, all notifications
* will pause and automatically resume after 2 seconds.
*
* If users keep tapping on the stream, once timeouts
Expand All @@ -19,21 +19,17 @@ import { isMouse } from '@/core/utils'
export function useTouchEvents() {
const { timeouts, config } = useStore()

let resumeTimeout: ReturnType<typeof setTimeout>

function pauseTouch(event: PointerEvent) {
if (!isMouse(event)) {
timeouts.clearDebounceTimeout()
timeouts.pause()
clearTimeout(resumeTimeout)

resumeTimeout = setTimeout(() => {
timeouts.resume()
}, 2000)
timeouts.resumeWithDebounce(2000)
}
}

onBeforeUnmount(() => {
clearTimeout(resumeTimeout)
timeouts.clearDebounceTimeout()
})

return computed(() =>
Expand Down
92 changes: 71 additions & 21 deletions packages/notivue/NotivueSwipe/NotivueSwipe.vue
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,23 @@ import {
import { useStore } from '@/core/useStore'
import { isMouse } from '@/core/utils'
import { NotificationTypeKeys as NType } from '@/core/constants'
import { DEFAULT_PROPS } from './constants'
import { DEFAULT_PROPS, DEBOUNCE, RETURN_DUR } from './constants'
import type { NotivueSwipeProps } from 'notivue'
/**
* MOUSE - Notivue's mouse events (get from 'useMouseEvents') will still handle the pause/resume logic
* on hover. NotivueSwipe will only additionally pause timeouts while swiping and resume them
* when clearing.
*
* TOUCH / PEN - Notivue's touch events (get from 'useTouchEvents') execution is prevented when
* using NotivueSwipe. That's because a more granular timeout control is required due
* to all possible interactions hence the whole touch logic is handled here.
*
* When releasing, leaving or clearing a notification via Swipe a small debounce time is added to
* improve UX.
*/
// Store
const { timeouts, elements, animations } = useStore()
Expand All @@ -40,12 +53,8 @@ const isEnabled = computed(
props.item.duration < Infinity
)
const lastItemContainer = computed(() => elements.items.value[elements.items.value.length - 1])
// Internal
const RETURN_DUR = 300
const itemRef = ref<HTMLElement | null>(null)
const state = shallowReactive({
Expand Down Expand Up @@ -114,11 +123,14 @@ function shouldSwipe(e: PointerEvent) {
function setTargetPosition() {
if (!itemRef.value) return
const { offsetLeft, clientWidth } = itemRef.value
// Must use clientWidth so that scale transform is not taken into account
const { clientWidth } = itemRef.value
const { left } = itemRef.value.getBoundingClientRect()
setState({
targetLeft: offsetLeft,
targetRight: offsetLeft + clientWidth,
targetLeft: left,
targetRight: left + clientWidth,
targetWidth: clientWidth,
})
}
Expand All @@ -137,6 +149,20 @@ function isPointerInside(e: PointerEvent) {
return e.clientX > state.targetLeft && e.clientX < state.targetRight
}
function getDebounceMs(e: PointerEvent) {
return isMouse(e) ? DEBOUNCE.Mouse : DEBOUNCE.Touch
}
function pauseTimeouts() {
timeouts.clearDebounceTimeout()
timeouts.pause()
}
function resumeTimeouts(ms: number) {
timeouts.clearDebounceTimeout()
timeouts.resumeWithDebounce(ms)
}
/* ====================================================================================
* Event Handlers
* ==================================================================================== */
Expand All @@ -149,8 +175,8 @@ function onPointerDown(e: PointerEvent) {
if (!shouldSwipe(e)) return
/**
* Prevents the `useTouchEvents` handler to fire, which is what
* we're looking for so it doesn't interfere.
* Prevents `useTouchEvents` events to fire, which is what
* we're looking for so they doen't interfere with NotivueSwipe logic.
*/
e.stopPropagation()
Expand All @@ -160,10 +186,25 @@ function onPointerDown(e: PointerEvent) {
if (exclude.value) {
const excludedEls = Array.from(itemRef.value.querySelectorAll(exclude.value))
if (excludedEls.includes(e.target as HTMLElement)) return
if (excludedEls.includes(e.target as HTMLElement)) {
/**
* When tapping an excluded element, we want to pause and resume timeouts
* after a bit, keeping the same behavior as Notivue touch events (useTouchEvents).
*
* This is not required when using the mouse as the pause/resume is already handled on
* hover by useMouseEvents.
*/
if (!isMouse(e)) {
pauseTimeouts()
resumeTimeouts(DEBOUNCE.TouchExternal)
}
return
}
}
if (!isMouse(e)) timeouts.pause()
if (!isMouse(e)) pauseTimeouts()
setState({
startX: e.clientX,
Expand Down Expand Up @@ -199,18 +240,23 @@ function onPointerMoveClear(e: PointerEvent) {
props.item.destroy()
if (isMouse(e) && isPointerInside(e)) {
const isLastItem = lastItemContainer.value.contains(itemRef.value)
if (!isLastItem) timeouts.pause()
const sortedContainers = elements.getSortedItems()
const isLastItem = sortedContainers[sortedContainers.length - 1].contains(itemRef.value)
if (!isLastItem) pauseTimeouts()
} else {
timeouts.resume()
resumeTimeouts(getDebounceMs(e))
}
const animDuration = parseFloat(animations.getTransitionData()?.duration ?? 0) * 1000
setTimeout(() => (state.isClearing = false), animDuration)
window.setTimeout(() => (state.isClearing = false), animDuration)
}
/**
* Never triggered if the notification has been closed on pointer move.
* Triggered when the notification is swiped and then released but not enough
* to be cleared.
*
* Callback logic is not executed if the notification gets closed while swiping.
*/
function onPointerUp(e: PointerEvent) {
if (!shouldSwipe(e)) return
Expand All @@ -221,9 +267,9 @@ function onPointerUp(e: PointerEvent) {
if (state.isClearing || state.isLocked) return
if (isMouse(e) && isPointerInside(e)) {
timeouts.pause()
pauseTimeouts()
} else {
timeouts.resume()
resumeTimeouts(getDebounceMs(e))
}
setReturnStyles()
Expand All @@ -235,9 +281,12 @@ function onPointerUp(e: PointerEvent) {
isLocked: true,
})
setTimeout(() => (state.isLocked = false), RETURN_DUR)
window.setTimeout(() => (state.isLocked = false), RETURN_DUR)
}
/**
* Triggered when the pointer leaves the notification bounding box while swiping.
*/
function onPointerLeave(e: PointerEvent) {
if (!shouldSwipe(e)) return
Expand All @@ -255,7 +304,7 @@ function onPointerLeave(e: PointerEvent) {
isLocked: false,
})
timeouts.resume()
resumeTimeouts(getDebounceMs(e))
}
/* ====================================================================================
Expand Down Expand Up @@ -302,6 +351,7 @@ watch(
})
onCleanup(() => {
// timeouts.clearDebounceTimeout()
removeListeners()
resetDragStyles()
// No need to reset target position as they will be recomputed on next enabling
Expand Down
8 changes: 8 additions & 0 deletions packages/notivue/NotivueSwipe/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,11 @@ export const DEFAULT_PROPS = {
disabled: false,
threshold: 0.5,
} as const

export const RETURN_DUR = 300

export const DEBOUNCE = {
Mouse: 200,
Touch: 1000,
TouchExternal: 1400,
}
Loading

0 comments on commit 9c0f803

Please sign in to comment.