Skip to content

Commit d811614

Browse files
committed
feat: new updates
1 parent 4df119c commit d811614

14 files changed

+4967
-4418
lines changed

package.json

+7-7
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"type": "module",
33
"version": "0.0.0",
44
"private": true,
5-
"packageManager": "pnpm@8.15.4",
5+
"packageManager": "pnpm@9.12.1",
66
"scripts": {
77
"lint": "eslint .",
88
"lint:fix": "eslint . --fix",
@@ -12,11 +12,11 @@
1212
"build:playground": "pnpm build:package && pnpm --filter playground build"
1313
},
1414
"devDependencies": {
15-
"@antfu/eslint-config": "^2.6.4",
16-
"@changesets/cli": "^2.27.1",
17-
"@playwright/test": "^1.41.2",
18-
"@rushstack/eslint-patch": "^1.3.3",
19-
"@vue/eslint-config-typescript": "^12.0.0",
20-
"eslint": "^8.56.0"
15+
"@antfu/eslint-config": "^3.7.3",
16+
"@changesets/cli": "^2.27.9",
17+
"@playwright/test": "^1.48.0",
18+
"@rushstack/eslint-patch": "^1.10.4",
19+
"@vue/eslint-config-typescript": "^14.1.1",
20+
"eslint": "^9.12.0"
2121
}
2222
}

packages/vaul-vue/package.json

+21-21
Original file line numberDiff line numberDiff line change
@@ -40,28 +40,28 @@
4040
"vue": "^3.3.0"
4141
},
4242
"dependencies": {
43-
"@vueuse/core": "^10.8.0",
44-
"radix-vue": "^1.4.9",
45-
"vue": "^3.4.5"
43+
"@vueuse/core": "^11.1.0",
44+
"radix-vue": "^1.9.7"
4645
},
4746
"devDependencies": {
48-
"@rushstack/eslint-patch": "^1.3.3",
49-
"@tsconfig/node18": "^18.2.2",
50-
"@types/jsdom": "^21.1.6",
51-
"@types/node": "^18.19.3",
52-
"@vitejs/plugin-vue": "^4.5.2",
53-
"@vue/eslint-config-typescript": "^12.0.0",
54-
"@vue/test-utils": "^2.4.3",
55-
"@vue/tsconfig": "^0.5.0",
56-
"eslint": "^8.49.0",
57-
"eslint-plugin-vue": "^9.17.0",
58-
"jsdom": "^23.0.1",
59-
"npm-run-all2": "^6.1.1",
60-
"typescript": "~5.3.0",
61-
"vite": "^5.0.10",
62-
"vite-plugin-css-injected-by-js": "^3.3.1",
63-
"vite-plugin-dts": "^3.7.0",
64-
"vitest": "^1.0.4",
65-
"vue-tsc": "^1.8.25"
47+
"@rushstack/eslint-patch": "^1.10.4",
48+
"@tsconfig/node18": "^18.2.4",
49+
"@types/jsdom": "^21.1.7",
50+
"@types/node": "^18.19.55",
51+
"@vitejs/plugin-vue": "^5.1.4",
52+
"@vue/eslint-config-typescript": "^14.1.1",
53+
"@vue/test-utils": "^2.4.6",
54+
"@vue/tsconfig": "^0.5.1",
55+
"eslint": "^9.12.0",
56+
"eslint-plugin-vue": "^9.29.0",
57+
"jsdom": "^25.0.1",
58+
"npm-run-all2": "^6.2.3",
59+
"typescript": "~5.6.3",
60+
"vite": "^5.4.9",
61+
"vite-plugin-css-injected-by-js": "^3.5.2",
62+
"vite-plugin-dts": "^4.2.4",
63+
"vitest": "^2.1.3",
64+
"vue": "^3.5.12",
65+
"vue-tsc": "^2.1.6"
6666
}
6767
}

packages/vaul-vue/src/DrawerRoot.vue

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<script setup lang="ts">
2-
import { DialogRoot } from 'radix-vue'
32
import { useVModel } from '@vueuse/core'
4-
import { type WritableComputedRef, computed, toRefs } from 'vue'
3+
import { DialogRoot } from 'radix-vue'
4+
import { computed, toRefs, type WritableComputedRef } from 'vue'
55
import { provideDrawerRootContext } from './context'
66
import {
77
CLOSE_THRESHOLD,

packages/vaul-vue/src/browser.ts

+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
export function isMobileFirefox(): boolean | undefined {
2+
const userAgent = navigator.userAgent
3+
return (
4+
typeof window !== 'undefined'
5+
&& ((/Firefox/.test(userAgent) && /Mobile/.test(userAgent)) // Android Firefox
6+
|| /FxiOS/.test(userAgent)) // iOS Firefox
7+
)
8+
}
9+
10+
export function isMac(): boolean | undefined {
11+
return testPlatform(/^Mac/)
12+
}
13+
14+
export function isIPhone(): boolean | undefined {
15+
return testPlatform(/^iPhone/)
16+
}
17+
18+
export function isSafari(): boolean | undefined {
19+
return /^((?!chrome|android).)*safari/i.test(navigator.userAgent)
20+
}
21+
22+
export function isIPad(): boolean | undefined {
23+
return (
24+
testPlatform(/^iPad/)
25+
// iPadOS 13 lies and says it's a Mac, but we can distinguish by detecting touch support.
26+
|| (isMac() && navigator.maxTouchPoints > 1)
27+
)
28+
}
29+
30+
export function isIOS(): boolean | undefined {
31+
return isIPhone() || isIPad()
32+
}
33+
34+
export function testPlatform(re: RegExp): boolean | undefined {
35+
return typeof window !== 'undefined' && window.navigator != null ? re.test(window.navigator.platform) : undefined
36+
}

packages/vaul-vue/src/constants.ts

+12
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,15 @@ export const TRANSITIONS = {
44
}
55

66
export const VELOCITY_THRESHOLD = 0.4
7+
8+
export const CLOSE_THRESHOLD = 0.25
9+
10+
export const SCROLL_LOCK_TIMEOUT = 100
11+
12+
export const BORDER_RADIUS = 8
13+
14+
export const NESTED_DISPLACEMENT = 16
15+
16+
export const WINDOW_TOP_OFFSET = 26
17+
18+
export const DRAG_CLASS = 'vaul-dragging'

packages/vaul-vue/src/context.ts

+31-21
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,46 @@
11
import type { ComponentPublicInstance, Ref } from 'vue'
22
import { createContext } from 'radix-vue'
3+
import type { DialogRootProps } from 'radix-vue'
34
import type { DrawerDirection } from './types'
45

56
export interface DrawerRootContext {
6-
open: Ref<boolean>
7-
isOpen: Ref<boolean>
8-
modal: Ref<boolean>
7+
drawerRef: Ref<ComponentPublicInstance | null> //
8+
overlayRef: Ref<ComponentPublicInstance | null> //
9+
onPress: (event: PointerEvent) => void //
10+
onRelease: (event: PointerEvent) => void //
11+
onDrag: (event: PointerEvent) => void //
12+
onNestedDrag: (percentageDragged: number) => void //
13+
onNestedOpenChange: (o: boolean) => void //
14+
onNestedRelease: (o: boolean) => void //
15+
dismissible: Ref<boolean>
16+
isOpen: Ref<boolean> //
17+
isDragging: Ref<boolean> //
18+
keyboardIsOpen: Ref<boolean> //
19+
snapPointsOffset: Ref<number[]> //
20+
snapPoints: Ref<(number | string)[] | undefined> //
21+
activeSnapPointIndex?: number | null // **
22+
modal: Ref<boolean> //
23+
shouldFade: Ref<boolean>
24+
activeSnapPoint: Ref<number | string | null | undefined>
25+
closeDrawer: () => void
26+
open: Ref<boolean> //
27+
direction: Ref<DrawerDirection>
28+
shouldScaleBackground: Ref<boolean | undefined>
29+
setBackgroundColorOnScale: boolean // **
30+
noBodyStyles: boolean // **
31+
handleOnly?: boolean
32+
container?: HTMLElement | null
33+
autoFocus?: boolean
34+
shouldAnimate?: Ref<boolean | undefined>
35+
36+
/** vaul-vue */
937
hasBeenOpened: Ref<boolean>
1038
isVisible: Ref<boolean>
11-
drawerRef: Ref<ComponentPublicInstance | null>
12-
overlayRef: Ref<ComponentPublicInstance | null>
13-
isDragging: Ref<boolean>
1439
dragStartTime: Ref<Date | null>
1540
isAllowedToDrag: Ref<boolean>
16-
snapPoints: Ref<(number | string)[] | undefined>
17-
keyboardIsOpen: Ref<boolean>
18-
activeSnapPoint: Ref<number | string | null | undefined>
1941
pointerStart: Ref<number>
20-
dismissible: Ref<boolean>
2142
drawerHeightRef: Ref<number>
22-
snapPointsOffset: Ref<number[]>
23-
direction: Ref<DrawerDirection>
24-
onPress: (event: PointerEvent) => void
25-
onDrag: (event: PointerEvent) => void
26-
onRelease: (event: PointerEvent) => void
27-
closeDrawer: () => void
28-
shouldFade: Ref<boolean>
2943
fadeFromIndex: Ref<number | undefined>
30-
shouldScaleBackground: Ref<boolean | undefined>
31-
onNestedDrag: (percentageDragged: number) => void
32-
onNestedRelease: (o: boolean) => void
33-
onNestedOpenChange: (o: boolean) => void
3444
emitClose: () => void
3545
emitDrag: (percentageDragged: number) => void
3646
emitRelease: (open: boolean) => void

packages/vaul-vue/src/controls.ts

+5-5
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
import { computed, onUnmounted, ref, watch, watchEffect } from 'vue'
21
import type { ComponentPublicInstance, Ref } from 'vue'
2+
import type { DrawerRootContext } from './context'
3+
import type { DrawerDirection } from './types'
34
import { isClient } from '@vueuse/core'
4-
import { dampenValue, getTranslate, isVertical, reset, set } from './helpers'
5+
import { computed, onUnmounted, ref, watch, watchEffect } from 'vue'
56
import { TRANSITIONS, VELOCITY_THRESHOLD } from './constants'
6-
import { useSnapPoints } from './useSnapPoints'
7+
import { dampenValue, getTranslate, isVertical, reset, set } from './helpers'
78
import { usePositionFixed } from './usePositionFixed'
8-
import type { DrawerRootContext } from './context'
9-
import type { DrawerDirection } from './types'
9+
import { useSnapPoints } from './useSnapPoints'
1010

1111
export const CLOSE_THRESHOLD = 0.25
1212

packages/vaul-vue/src/helpers.ts

+38-11
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { DrawerDirection } from './types'
1+
import type { AnyFunction, DrawerDirection } from './types'
22

33
interface Style {
44
[key: string]: string
@@ -60,7 +60,23 @@ export function reset(el: Element | HTMLElement | null, prop?: string) {
6060
}
6161
}
6262

63+
export function isVertical(direction: DrawerDirection) {
64+
switch (direction) {
65+
case 'top':
66+
case 'bottom':
67+
return true
68+
case 'left':
69+
case 'right':
70+
return false
71+
default:
72+
return direction satisfies never
73+
}
74+
}
75+
6376
export function getTranslate(element: HTMLElement, direction: DrawerDirection): number | null {
77+
if (!element)
78+
return null
79+
6480
const style = window.getComputedStyle(element)
6581
const transform
6682
// @ts-expect-error some custom style only exist in certain browser
@@ -79,15 +95,26 @@ export function dampenValue(v: number) {
7995
return 8 * (Math.log(v + 1) - 2)
8096
}
8197

82-
export function isVertical(direction: DrawerDirection) {
83-
switch (direction) {
84-
case 'top':
85-
case 'bottom':
86-
return true
87-
case 'left':
88-
case 'right':
89-
return false
90-
default:
91-
return direction satisfies never
98+
export function assignStyle(element: HTMLElement | null | undefined, style: Partial<CSSStyleDeclaration>) {
99+
if (!element)
100+
return () => {}
101+
102+
const prevStyle = element.style.cssText
103+
Object.assign(element.style, style)
104+
105+
return () => {
106+
element.style.cssText = prevStyle
107+
}
108+
}
109+
110+
/**
111+
* Receives functions as arguments and returns a new function that calls all.
112+
*/
113+
export function chain<T>(...fns: T[]) {
114+
return (...args: T extends AnyFunction ? Parameters<T> : never) => {
115+
for (const fn of fns) {
116+
if (typeof fn === 'function')
117+
fn(...args)
118+
}
92119
}
93120
}

packages/vaul-vue/src/types.ts

+2
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,5 @@ export interface SnapPoint {
44
}
55

66
export type DrawerDirection = 'top' | 'bottom' | 'left' | 'right'
7+
8+
export type AnyFunction = (...args: any) => any

packages/vaul-vue/src/usePositionFixed.ts

+26-11
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,53 @@
1-
import { type Ref, onMounted, onUnmounted, ref, watch } from 'vue'
1+
import { onMounted, onUnmounted, type Ref, ref, watch } from 'vue'
2+
import { isSafari } from './browser'
23

34
interface BodyPosition {
45
position: string
56
top: string
67
left: string
78
height: string
9+
right: string
810
}
911

1012
interface PositionFixedOptions {
1113
isOpen: Ref<boolean>
1214
modal: Ref<boolean>
1315
nested: Ref<boolean>
1416
hasBeenOpened: Ref<boolean>
17+
preventScrollRestoration: Ref<boolean>
18+
noBodyStyles: Ref<boolean>
1519
}
1620

1721
let previousBodyPosition: BodyPosition | null = null
1822

1923
export function usePositionFixed(options: PositionFixedOptions) {
20-
const { isOpen, modal, nested, hasBeenOpened } = options
24+
const { isOpen, modal, nested, hasBeenOpened, preventScrollRestoration, noBodyStyles } = options
2125
const activeUrl = ref(typeof window !== 'undefined' ? window.location.href : '')
2226
const scrollPos = ref(0)
2327

2428
function setPositionFixed(): void {
25-
if (previousBodyPosition === null && isOpen.value) {
29+
if (!isSafari())
30+
return
31+
32+
// If previousBodyPosition is already set, don't set it again.
33+
if (previousBodyPosition === null && isOpen.value && !noBodyStyles.value) {
2634
previousBodyPosition = {
2735
position: document.body.style.position,
2836
top: document.body.style.top,
2937
left: document.body.style.left,
3038
height: document.body.style.height,
39+
right: 'unset',
3140
}
3241

3342
const { scrollX, innerHeight } = window
3443

35-
document.body.style.position = 'fixed'
36-
document.body.style.top = `-${scrollPos.value}px`
37-
document.body.style.left = `-${scrollX}px`
38-
document.body.style.right = '0px'
39-
document.body.style.height = 'auto'
44+
document.body.style.setProperty('position', 'fixed', 'important')
45+
Object.assign(document.body.style, {
46+
top: `${-scrollPos.value}px`,
47+
left: `${-scrollX}px`,
48+
right: '0px',
49+
height: 'auto',
50+
})
4051

4152
setTimeout(() => {
4253
requestAnimationFrame(() => {
@@ -52,7 +63,11 @@ export function usePositionFixed(options: PositionFixedOptions) {
5263
}
5364

5465
function restorePositionSetting(): void {
55-
if (previousBodyPosition !== null) {
66+
// All browsers on iOS will return true here.
67+
if (!isSafari())
68+
return
69+
70+
if (previousBodyPosition !== null && !noBodyStyles.value) {
5671
// Convert the position from "px" to Int
5772
const y = -Number.parseInt(document.body.style.top, 10)
5873
const x = -Number.parseInt(document.body.style.left, 10)
@@ -61,7 +76,7 @@ export function usePositionFixed(options: PositionFixedOptions) {
6176
Object.assign(document.body.style, previousBodyPosition)
6277

6378
requestAnimationFrame(() => {
64-
if (activeUrl.value !== window.location.href) {
79+
if (preventScrollRestoration && activeUrl.value !== window.location.href) {
6580
activeUrl.value = window.location.href
6681
return
6782
}
@@ -86,7 +101,7 @@ export function usePositionFixed(options: PositionFixedOptions) {
86101
})
87102
})
88103

89-
watch([isOpen, hasBeenOpened, activeUrl], () => {
104+
watch([isOpen, hasBeenOpened, activeUrl, modal, nested, setPositionFixed, restorePositionSetting], () => {
90105
if (nested.value || !hasBeenOpened.value)
91106
return
92107

0 commit comments

Comments
 (0)