35 |
36 |
37 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2018-present, Chung Hang (Hunter) Liu
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | THE SOFTWARE.
--------------------------------------------------------------------------------
/packages/nuxt/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2018-present, Chung Hang (Hunter) Liu
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | THE SOFTWARE.
--------------------------------------------------------------------------------
/packages/vue-final-modal/src/Component.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * vue-component-type-helpers
3 | * Copy from https://github.com/vuejs/language-tools/tree/master/packages/component-type-helpers
4 | */
5 |
6 | // export type ComponentType =
7 | // T extends new () => {} ? 1 :
8 | // T extends (...args: any) => any ? 2 :
9 | // 0
10 |
11 | export type ComponentProps =
12 | T extends new () => { $props: infer P } ? NonNullable
:
13 | T extends (props: infer P, ...args: any) => any ? P :
14 | {}
15 |
16 | export type ComponentSlots =
17 | T extends new () => { $slots: infer S } ? NonNullable :
18 | T extends (props: any, ctx: { slots: infer S; attrs: any; emit: any }, ...args: any) => any ? NonNullable :
19 | {}
20 |
21 | export type ComponentEmit =
22 | T extends new () => { $emit: infer E } ? NonNullable :
23 | T extends (props: any, ctx: { slots: any; attrs: any; emit: infer E }, ...args: any) => any ? NonNullable :
24 | {}
25 |
26 | // export type ComponentExposed =
27 | // T extends new () => infer E ? E :
28 | // T extends (props: any, ctx: any, expose: (exposed: infer E) => any, ...args: any) => any ? NonNullable :
29 | // {}
30 |
--------------------------------------------------------------------------------
/packages/vue-final-modal/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2018-present, Chung Hang (Hunter) Liu
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | THE SOFTWARE.
--------------------------------------------------------------------------------
/docs/components/content/ModalLongScrollPreview.vue:
--------------------------------------------------------------------------------
1 |
26 |
27 |
28 |
29 | Open Modal
30 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/viteplay/src/components/VueFinalModal/BottomSheet.example.vue:
--------------------------------------------------------------------------------
1 |
31 |
32 |
33 |
21 |
22 | ## Playground
23 |
24 | - [Stackblitz for Vue 3](https://stackblitz.com/github/vue-final/vue-final-modal/tree/master/examples/vue3)
25 | - [Stackblitz for Nuxt 3](https://stackblitz.com/github/vue-final/vue-final-modal/tree/master/examples/nuxt3)
26 |
27 | ## [Documentation](https://v4.vue-final-modal.org/)
28 |
29 | Checkout [Migration guide from v3](https://v4.vue-final-modal.org/get-started/guide/migration-guide).
30 |
31 | Looking for old version?
32 |
33 | - [vue-final-modal@3.x for Vue 3](https://v3.vue-final-modal.org/)
34 | - [vue-final-modal@2.x for Vue 2](https://v2.vue-final-modal.org/)
35 |
36 | ## Contribution Guide
37 |
38 | ```bash [pnpm]
39 | # Install packages
40 | pnpm install --shamefully-hoist
41 |
42 | # Build vue-final-modal library first
43 | pnpm build:vfm
44 |
45 | # Run both docs and viteplay
46 | pnpm dev
47 |
48 | # Run dev for vue-final-modal
49 | pnpm dev:vfm
50 |
51 | # Run docs: http://localhost:3000/
52 | pnpm dev:docs
53 |
54 | # Run viteplay: http://localhost:5173/
55 | pnpm dev:viteplay
56 | ```
57 |
58 | ## Contributors
59 |
60 | Thank you to all the people who already contributed to `vue-final-modal`!
61 |
62 |
63 |
64 |
65 |
66 | Made with [contributors-img](https://contrib.rocks).
67 |
68 | 🚀 If you have any ideas for optimization of `vue-final-modal`, feel free to open [issues](https://github.com/hunterliu1003/vue-final-modal/issues) or [pull requests](https://github.com/hunterliu1003/vue-final-modal/pulls).
69 |
--------------------------------------------------------------------------------
/docs/content/2.get-started/1.guide/1.concepts.md:
--------------------------------------------------------------------------------
1 | # Concepts
2 |
3 | These are the core concepts you need to know about vue-final-modal and its approach to modals management.
4 |
5 | ::alert{type=danger}
6 | ❗️ If you came from vue-final-modal 3.x, please check the [migration guide](/get-started/guide/migration-guide).
7 | ::
8 |
9 | ## What is VueFinalModal?
10 |
11 | `vue-final-modal` is a modal library built specifically for Vue.js and as such it makes some assumptions and enforces "best-practices" for your modals while being versatile and customizable.
12 |
13 | `vue-final-modal` is a collection of [composables](https://vuejs.org/guide/reusability/composables.html#what-is-a-composable) and Vue components, the main things that will be covered is how to use these components to build your own modal components and use them with [useModal()](/api/composables/use-modal) composable function.
14 |
15 | ### Built-in features
16 |
17 | ::list{type=success}
18 | - SSR (Nuxt 3) support. [Teleport](/api/components/vue-final-modal#teleportto) your modals to `'body'`{lang=ts} by default.
19 | - [`useModal()`{lang=ts}](http://localhost:3000/api/composables/use-modal) composable to manage your modal programmatically.
20 | - Trap keyboard focus within modal element (uses [focus-trap](https://github.com/focus-trap/focus-trap) for best web accessibility).
21 | - Full control to the zIndex of the nested modals (see [zIndexFn prop](/api/components/vue-final-modal#zindexfn)).
22 | - Customizable `` for the modal content and overlay.
23 | - Tiny footprint! Only 4.x kB! Vue Final Modal focuses on the core functionality of modals, leaves the complex CSS up to the developer.
24 | ::
25 |
26 | ### A Styleless component
27 |
28 | [\](/api/components/vue-final-modal) is a styleless component that provides the features that modal required as much as possible but leave the layout as a slot to developer.
29 |
30 | So you have to define the modal style by yourself.
31 |
32 | ::alert{type=info}
33 | Follow the [Setup section](/get-started/guide/setup) to learn how to use VueFinalModal.
34 | ::
35 |
36 | ## You Might Not Need VueFinalModal
37 |
38 | Although I created `vue-final-modal`, I still try not to use it as much as possible.
39 |
40 | Here are the articles that I highly recommended you to read before diving into VueFinalModal:
41 |
42 | - [Popups: 10 Problematic Trends and Alternatives](https://www.nngroup.com/articles/popups/)
43 | - [Can Hated Design Elements Be Made to Work?](https://www.nngroup.com/articles/making-hated-design-elements-work/)
44 | - [The Most Hated Online Advertising Techniques](https://www.nngroup.com/articles/most-hated-advertising-techniques/)
45 | - [Modal & Nonmodal Dialogs: When (& When Not) to Use Them](https://www.nngroup.com/articles/modal-nonmodal-dialog/)
46 | - [Modes in User Interfaces: When They Help and When They Hurt Users](https://www.nngroup.com/articles/modes/)
47 |
48 |
--------------------------------------------------------------------------------
/docs/content/4.use-cases/8.modal-drag-resize.md:
--------------------------------------------------------------------------------
1 | ---
2 | head.title: 'Modal Drag Resize | Examples'
3 | ---
4 |
5 | # Modal Drag Resize
6 |
7 | A drag and resize modal example.
8 |
9 | Here is a basic drag and resize modal example that using [vue3-drag-resize](https://github.com/kirillmurashov/vue-drag-resize/tree/vue3).
10 |
11 | ## Preview
12 |
13 | ::code-group
14 | ::code-block{label="Preview" preview}
15 | ::modal-drag-resize-preview
16 | ::
17 | ::
18 |
19 | ```vue [Preview.vue]
20 |
32 |
33 |
34 | open()">
35 | Open Modal
36 |
37 |
38 |
39 |
40 | ```
41 | ::
42 |
43 | ## `` component
44 |
45 | ::code-group
46 | ```vue [DragResizeModal.vue]
47 |
67 |
68 |
69 | emit('update:modelValue', val)"
75 | >
76 |
77 |
78 |
79 |
87 |
Hello World!
88 |
{{ top }} х {{ left }}
89 |
{{ width }} х {{ height }}
90 |
93 |
94 |
95 |
96 |
97 | ```
98 | ::
99 |
--------------------------------------------------------------------------------
/docs/content/1.index.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Vue Final Modal"
3 | description: "Vue Final Modal is the most powerful yet most light-weight modal library for Vue 3"
4 | navigation: false
5 | layout: page
6 | ---
7 |
8 | ::block-hero
9 | ---
10 | cta:
11 | - Get Started
12 | - /get-started/guide/concepts
13 | secondary:
14 | - Star on GitHub
15 | - https://github.com/vue-final/vue-final-modal
16 | snippet: npm install vue-final-modal@latest
17 | ---
18 |
19 | #title
20 | Modals made easy for Vue Developers
21 |
22 | #description
23 | The most powerful yet most light-weight modal library for Vue 3.
24 |
25 | #right
26 | ::
27 |
28 | ::buy-me-a-coffee
29 | ::
30 |
31 | ::card-grid
32 | #title
33 | Powerful Features
34 |
35 | #default
36 | ::card
37 | ---
38 | icon: ph:file-css-duotone
39 | ---
40 | #title
41 | Custom Styling
42 | #description
43 | Write your own CSS or bring libraries like Tailwind/WindiCSS.
44 | ::
45 |
46 | ::card
47 | ---
48 | icon: ph:file-code
49 | ---
50 | #title
51 | useModal
52 | #description
53 | Open and close modal programmatically with `useModal()`{lang=ts} composable function.
54 | ::
55 |
56 | ::card
57 | ---
58 | icon: tabler:brand-nuxt
59 | ---
60 | #title
61 | Support Nuxt 3
62 | #description
63 | SSR support with ``{lang=ts} by default.
64 | ::
65 |
66 | ::card
67 | ---
68 | icon: ph:file-ts
69 | ---
70 | #title
71 | Rewrite with Typescript
72 | #description
73 | Rewrite with Typescript for better DX.
74 | ::
75 |
76 | ::card
77 | ---
78 | icon: icon-park-outline:christmas-tree-one
79 | ---
80 | #title
81 | Only 7.x kB!
82 | #description
83 | Vue Final Modal focuses on the core functionality of modals, leaves the complex CSS up to the developer.
84 | ::
85 |
86 | #root
87 | ::
88 |
89 | ## Contribution Guide
90 |
91 | ```bash [pnpm]
92 | # Install packages
93 | pnpm install --shamefully-hoist
94 |
95 | # Build vue-final-modal library first
96 | pnpm build:vfm
97 |
98 | # Run both docs and viteplay
99 | pnpm dev
100 |
101 | # Run docs: http://localhost:3000/
102 | pnpm dev:docs
103 |
104 | # Run viteplay: http://localhost:5173/
105 | pnpm dev:viteplay
106 | ```
107 |
108 |
109 | ## Contributors
110 |
111 | Thank you to all the people who already contributed to `vue-final-modal`!
112 |
113 |
114 |
115 |
116 |
117 | Made with [contributors-img](https://contrib.rocks).
118 |
119 | 🚀 If you have any ideas for optimization of `vue-final-modal`, feel free to open [issues](https://github.com/hunterliu1003/vue-final-modal/issues) or [pull requests](https://github.com/hunterliu1003/vue-final-modal/pulls).
120 |
--------------------------------------------------------------------------------
/docs/content/4.use-cases/9.modal-long-scroll.md:
--------------------------------------------------------------------------------
1 | ---
2 | head.title: 'Modal Long Scroll | Examples'
3 | ---
4 |
5 | # Modal Long Scroll
6 |
7 | A long scrollable modal example like [Bootstrap Modal scrolling-long-content](https://getbootstrap.com/docs/5.3/components/modal/#scrolling-long-content)
8 |
9 | ## Preview
10 |
11 | ::code-group
12 | ::code-block{label="Preview" preview}
13 | ::modal-long-scroll-preview
14 | ::
15 | ::
16 |
17 | ```vue [Preview.vue]
18 |
43 |
44 |
45 |
46 | Open Modal
47 |
48 |
49 |
50 |
51 |
52 | ```
53 | ::
54 |
55 | ## `` component
56 |
57 | ::code-group
58 | ```vue [ModalLongScroll.vue]
59 |
71 |
72 |
73 | emit('update:modelValue', val)"
78 | >
79 |
21 |
22 | ```
23 |
24 | ## Usage
25 |
26 | ### Passing Props and Events
27 |
28 | ```ts
29 | import { VueFinalModal, useModal } from 'vue-final-modal'
30 |
31 | const { open, close, destroy, options, patchOptions } = useModal({
32 | // Open the modal or not when the modal was created, the default value is `false`.
33 | defaultModelValue: false,
34 | /**
35 | * If set `keepAlive` to `true`:
36 | * 1. The `displayDirective` will be set to `show` by default.
37 | * 2. The modal component will not be removed after the modal closed until you manually execute `destroy()`.
38 | */
39 | keepAlive: false,
40 | // `component` is optional and the default value is ``.
41 | component: VueFinalModal,
42 | attrs: {
43 | // Bind props to the modal component (VueFinalModal in this case).
44 | clickToClose: true,
45 | escToClose: true,
46 | // Bind events to the modal component (VueFinalModal in this case).
47 | // Any custom events can be listened for when prefixed with "on", e.g. "onEventName".
48 | onBeforeOpen() { /* on before open */ },
49 | onOpened() { /* on opened */ },
50 | onBeforeClose() { /* on before close */ },
51 | onClosed() { /* on closed */ },
52 | }
53 | })
54 |
55 | // Open the modal
56 | open().then(() => { /* Do something after modal opened */ })
57 | // Close the modal
58 | close().then(() => { /* Do something after modal closed */ })
59 | // Destroy the modal manually, it only be needed when the `keepAlive` is set to `true`
60 | destroy()
61 | // Checkout the modal options
62 | options // the state of the dynamic modal
63 |
64 | // Overwrite the modal options
65 | patchOptions({
66 | attrs: {
67 | // Overwrite the modal's props
68 | clickToClose: false,
69 | escToClose: false,
70 | }
71 | })
72 | ```
73 |
74 | ### Passing Slots
75 |
76 | #### with `String`
77 |
78 |
79 | ```ts
80 | import { VueFinalModal, useModal } from 'vue-final-modal'
81 |
82 | const modalInstance = useModal({
83 | component: VueFinalModal,
84 | attrs: { ... },
85 | slots: {
86 | default: '
The content of the modal
'
87 | }
88 | })
89 | ```
90 |
91 | ::alert{type=warning}
92 | Security Note: https://vuejs.org/api/built-in-directives.html#v-html
93 | Dynamically rendering arbitrary HTML on your website can be very dangerousbecause it can easily lead to XSS attacks. Only use v-html on trusted content and never on user-provided content.
94 | ::
95 |
96 | #### with `Component`
97 |
98 |
99 | ```ts
100 | import { VueFinalModal, useModal } from 'vue-final-modal'
101 | // ModalContent is the component you want to put into the modal content
102 | import ModalContent from './ModalContent.vue'
103 |
104 |
105 | const modalInstance = useModal({
106 | component: VueFinalModal,
107 | attrs: { ... },
108 | slots: {
109 | // You can import your own component as a slot and put it to `slots.default` without binding props and events.
110 | default: ModalContent
111 | }
112 | })
113 | ```
114 |
115 | #### with `Component`, `Props` and `Events`
116 |
117 | ```ts
118 | import { VueFinalModal, useModal, useModalSlot } from 'vue-final-modal'
119 | // ModalContent is the component you want to put into the modal content
120 | import ModalContent from './ModalContent.vue'
121 |
122 | const modalInstance = useModal({
123 | component: VueFinalModal,
124 | attrs: { ... },
125 | slots: {
126 | default: useModalSlot({
127 | component: ModalContent,
128 | attrs: {
129 | // Bind ModalContent props
130 | title: 'Hello world!'
131 | // Bind ModalContent events
132 | onConfirm() { }
133 | }
134 | })
135 | }
136 | })
137 | ```
138 |
139 | ::alert{type=info}
140 | `useModalSlot()` is a function that provides better DX for type checking. It just returns the same object you passed in.
141 | ::
142 |
143 | ## Type Declarations
144 |
145 | ::alert{type=info}
146 | Checkout [Types section](/get-started/guide/types).
147 | ::
148 |
--------------------------------------------------------------------------------
/docs/components/content/Playground.vue:
--------------------------------------------------------------------------------
1 |
25 |
26 |
27 |
28 |
modelValue:
29 |
30 |
31 |
displayDirective:
32 |
33 |
37 |
38 |
39 |
hideOverlay:
40 |
41 |
42 |
overlayTransition:
43 |
44 |
48 |
49 |
50 |
contentTransition:
51 |
52 |
56 |
57 |
58 |
clickToClose:
59 |
60 |
61 |
escToClose:
62 |
63 |
64 |
background:
65 |
66 |
70 |
71 |
72 |
lockScroll:
73 |
74 |
75 |
reserveScrollBarGap:
76 |
77 |
78 |
swipeToClose:
79 |
80 |
84 |
85 |
86 |
87 |
88 |
91 |
94 |
95 |
96 |
112 |
113 | Hello World!
114 |
115 |
Magna deserunt nulla aliquip velit aute. Et occaecat elit nulla excepteur labore cupidatat. Duis culpa mollit commodo dolor qui Lorem qui laborum elit elit Lorem occaecat. Commodo eiusmod esse voluptate officia amet quis occaecat aliqua. Proident do irure amet ut occaecat dolor laboris consectetur.
Magna deserunt nulla aliquip velit aute. Et occaecat elit nulla excepteur labore cupidatat. Duis culpa mollit commodo dolor qui Lorem qui laborum elit elit Lorem occaecat. Commodo eiusmod esse voluptate officia amet quis occaecat aliqua. Proident do irure amet ut occaecat dolor laboris consectetur.
128 |
131 |
132 |
133 |
134 |
135 |
136 |
141 | ```
142 | ::
143 |
--------------------------------------------------------------------------------
/packages/vue-final-modal/src/useSwipeToClose.ts:
--------------------------------------------------------------------------------
1 | import { useEventListener } from '@vueuse/core'
2 | import type { Ref } from 'vue'
3 | import { computed, ref, watch } from 'vue'
4 | import type VueFinalModal from './components/VueFinalModal/VueFinalModal.vue'
5 | import { useSwipeable } from './useSwipeable'
6 | import { clamp, noop } from './utils'
7 |
8 | export function useSwipeToClose(
9 | props: InstanceType['$props'],
10 | options: {
11 | vfmContentEl: Ref
12 | modelValueLocal: Ref
13 | },
14 | ) {
15 | const { vfmContentEl, modelValueLocal } = options
16 | const LIMIT_DISTANCE = 0.1
17 | const LIMIT_SPEED = 300
18 |
19 | const swipeBannerEl = ref()
20 | const swipeEl = computed(() => {
21 | if (props.swipeToClose === undefined || props.swipeToClose === 'none')
22 | return undefined
23 | else
24 | return (props.showSwipeBanner ? swipeBannerEl.value : vfmContentEl.value)
25 | })
26 |
27 | const offset = ref(0)
28 | const isCollapsed = ref(true)
29 |
30 | let stopSelectionChange = noop
31 | let shouldCloseModal = true
32 | let swipeStart: number
33 | let allowSwipe = false
34 |
35 | const { lengthX, lengthY, direction: _direction, isSwiping } = useSwipeable(swipeEl, {
36 | threshold: props.threshold,
37 | onSwipeStart(e) {
38 | stopSelectionChange = useEventListener(document, 'selectionchange', () => {
39 | isCollapsed.value = window.getSelection()?.isCollapsed
40 | })
41 | swipeStart = new Date().getTime()
42 | allowSwipe = canSwipe(e?.target)
43 | },
44 | onSwipe() {
45 | if (!allowSwipe)
46 | return
47 | if (!isCollapsed.value)
48 | return
49 | if (_direction.value !== props.swipeToClose)
50 | return
51 | if (_direction.value === 'up') {
52 | const offsetY = clamp(Math.abs(lengthY.value || 0), 0, swipeEl.value?.offsetHeight || 0) - (props.threshold || 0)
53 | offset.value = offsetY
54 | }
55 | else if (_direction.value === 'down') {
56 | const offsetY = clamp(Math.abs(lengthY.value || 0), 0, swipeEl.value?.offsetHeight || 0) - (props.threshold || 0)
57 | offset.value = -offsetY
58 | }
59 | else if (_direction.value === 'right') {
60 | const offsetX = clamp(Math.abs(lengthX.value || 0), 0, swipeEl.value?.offsetWidth || 0) - (props.threshold || 0)
61 | offset.value = -offsetX
62 | }
63 | else if (_direction.value === 'left') {
64 | const offsetX = clamp(Math.abs(lengthX.value || 0), 0, swipeEl.value?.offsetWidth || 0) - (props.threshold || 0)
65 | offset.value = offsetX
66 | }
67 | },
68 | onSwipeEnd(e, _direction) {
69 | stopSelectionChange()
70 | if (!isCollapsed.value) {
71 | isCollapsed.value = true
72 | return
73 | }
74 |
75 | const swipeEnd = new Date().getTime()
76 |
77 | const validDirection = _direction === props.swipeToClose
78 | const validDistance = (() => {
79 | if (_direction === 'up' || _direction === 'down')
80 | return Math.abs(lengthY?.value || 0) > LIMIT_DISTANCE * (swipeEl.value?.offsetHeight || 0)
81 | else if (_direction === 'left' || _direction === 'right')
82 | return Math.abs(lengthX?.value || 0) > LIMIT_DISTANCE * (swipeEl.value?.offsetWidth || 0)
83 | })()
84 | const validSpeed = swipeEnd - swipeStart <= LIMIT_SPEED
85 |
86 | if (shouldCloseModal && allowSwipe && validDirection && (validDistance || validSpeed)) {
87 | modelValueLocal.value = false
88 | return
89 | }
90 |
91 | offset.value = 0
92 | },
93 | })
94 |
95 | const bindSwipe = computed(() => {
96 | if (props.swipeToClose === 'none')
97 | return
98 | const translateDirection = (() => {
99 | switch (props.swipeToClose) {
100 | case 'up':
101 | case 'down':
102 | return 'translateY'
103 | case 'left':
104 | case 'right':
105 | return 'translateX'
106 | }
107 | })()
108 | return {
109 | class: { 'vfm-bounce-back': !isSwiping.value },
110 | style: { transform: `${translateDirection}(${-offset.value}px)` },
111 | }
112 | })
113 |
114 | watch(
115 | () => isCollapsed.value,
116 | (val) => {
117 | if (!val)
118 | offset.value = 0
119 | },
120 | )
121 |
122 | watch(
123 | () => modelValueLocal.value,
124 | (val) => {
125 | if (val)
126 | offset.value = 0
127 | },
128 | )
129 |
130 | watch(
131 | () => offset.value,
132 | (newValue, oldValue) => {
133 | switch (props.swipeToClose) {
134 | case 'down':
135 | case 'right':
136 | shouldCloseModal = newValue < oldValue
137 | break
138 | case 'up':
139 | case 'left':
140 | shouldCloseModal = newValue > oldValue
141 | break
142 | }
143 | },
144 | )
145 |
146 | function onTouchStartSwipeBanner(e: TouchEvent) {
147 | if (props.preventNavigationGestures)
148 | e.preventDefault()
149 | }
150 |
151 | function canSwipe(target?: null | EventTarget): boolean {
152 | const tagName = (target as HTMLElement)?.tagName
153 | if (!tagName || ['INPUT', 'TEXTAREA'].includes(tagName))
154 | return false
155 |
156 | const allow = (() => {
157 | switch (props.swipeToClose) {
158 | case 'up':
159 | return (target as HTMLElement)?.scrollTop + (target as HTMLElement)?.clientHeight === (target as HTMLElement)?.scrollHeight
160 | case 'left':
161 | return (target as HTMLElement)?.scrollLeft + (target as HTMLElement)?.clientWidth === (target as HTMLElement)?.scrollWidth
162 | case 'down':
163 | return (target as HTMLElement)?.scrollTop === 0
164 | case 'right':
165 | return (target as HTMLElement)?.scrollLeft === 0
166 | default:
167 | return false
168 | }
169 | })()
170 |
171 | if (target === swipeEl.value)
172 | return allow
173 | else
174 | return allow && canSwipe((target as HTMLElement)?.parentElement)
175 | }
176 |
177 | return {
178 | vfmContentEl,
179 | swipeBannerEl,
180 | bindSwipe,
181 | onTouchStartSwipeBanner,
182 | }
183 | }
184 |
--------------------------------------------------------------------------------
/docs/public/logo-dark.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/docs/public/logo-light.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/docs/content/2.get-started/1.guide/3.migration-guide.md:
--------------------------------------------------------------------------------
1 | # Migration Guide
2 |
3 | This document should help you navigate the tricky path of migrating between different versions of vue-final-modal that introduced breaking changes:
4 |
5 | ## Migrating from 3.x to 4.0
6 |
7 | vue-final-modal 4.0 introduced a lot of breaking changes.
8 |
9 | You should treat 4.x as a different library and read the documentation carefully, however these are some pointers towards more of the significant changes.
10 |
11 | To migrate from `3.x` you need to understand some of the changes to the public API and note that there is not direct path to upgrade and if you have a large app and you don't need some of the newer features, consider staying at the 3.x releases.
12 |
13 | ## Step by step
14 |
15 | ### Removed `vfmPlugin`
16 |
17 | #### In Vue 3
18 |
19 | So this:
20 |
21 | ```ts [main.ts]
22 | import { vfmPlugin } from 'vue-final-modal'
23 |
24 | app.use(vfmPlugin)
25 | ```
26 |
27 | Will be re-written as this:
28 |
29 | ```ts [main.ts]
30 | import { createVfm } from 'vue-final-modal'
31 | const vfm = createVfm()
32 | app.use(vfm)
33 | ```
34 |
35 | #### In Nuxt 3
36 |
37 | So this:
38 |
39 | ```ts [./plugins/vue-final-modal.ts]
40 | import { defineNuxtPlugin } from '#imports'
41 | import { vfmPlugin } from 'vue-final-modal'
42 |
43 | export default defineNuxtPlugin(nuxtApp => {
44 | nuxtApp.vueApp.use(vfmPlugin)
45 | })
46 | ```
47 |
48 | Will be re-written as this:
49 |
50 | ```ts [./plugins/vue-final-modal.ts]
51 | import { createVfm } from 'vue-final-modal'
52 |
53 | export default defineNuxtPlugin((nuxtApp) => {
54 | const vfm = createVfm() as any
55 |
56 | nuxtApp.vueApp.use(vfm)
57 | })
58 | ```
59 |
60 | ### Import Required CSS
61 |
62 | #### In Vue 3
63 |
64 | ```ts [main.ts]
65 | import 'vue-final-modal/style.css'
66 | ```
67 |
68 | #### In Nuxt 3
69 |
70 | ```ts [./nuxt.config.ts]
71 | export default defineNuxtConfig({
72 | css: ['vue-final-modal/style.css'],
73 | })
74 | ```
75 |
76 | ### Removed Dynamic Modal `$vfm.show`
77 |
78 | If you are using `$vfm.show()`{lang=ts} to create dynamic modals. You have to re-written them with [useModal()](/api/composables/use-modal) composable.
79 |
80 | So this:
81 |
82 | ```ts
83 | this.$vfm.show({
84 | component: ModalConfirm,
85 | bind: {
86 | name: 'ModalConfirmName'
87 | },
88 | on: {
89 | confirm() {
90 | this.$vfm.hide('ModalConfirmName')
91 | },
92 | opened() {
93 | console.log('modal opened')
94 | },
95 | },
96 | slots: {
97 | default: '