Skip to content

Commit

Permalink
chore(lib): update Modal
Browse files Browse the repository at this point in the history
- `Modal` migrates to Vue 3. Changes sufficient to render the
  documentation page of `Modal` are made. The commit of changes to
  the documentation page will follow this commit.
- In `src/components/modal/Modal.vue`,
    - `v-model` binding is replaced so that the default `v-model`
      works.
        - `active` prop --> `modelValue`
        - `update:active` event --> `update:modelValue`
    - Appending `$el` to body is not done at `beforeMount` but at
      `mounted`, because `$el` is not available at `beforeMount`.
    - Events to be emitted are listed in `emits`.
    - Automatic ESLint fix is applied.
- In `src/components/modal/index.js`,
    - `open` creates a new component instance by the `createApp` API,
      and then mount it on a fresh div element instead of extending an
      existing Vue instance. Because Vue 3 no longer supports
      extention of a Vue instance. A component instance returned by
      this function is not exactly a `Modal` but exposes `close`
      function so that it can be programmatically closed. A created
      app unmounts itself when the modal is closed.
    - Because of the above change, the component specified to the
      `component` option cannot resolve components registered to the
      current app. The component has to explicitly reference
      components that it depends on.
  • Loading branch information
kikuomax committed Jul 7, 2023
1 parent d3ec84f commit 8b05481
Show file tree
Hide file tree
Showing 2 changed files with 60 additions and 35 deletions.
39 changes: 21 additions & 18 deletions src/components/modal/Modal.vue
Original file line number Diff line number Diff line change
Expand Up @@ -61,13 +61,8 @@ export default {
directives: {
trapFocus
},
// deprecated, to replace with default 'value' in the next breaking change
model: {
prop: 'active',
event: 'update:active'
},
props: {
active: Boolean,
modelValue: Boolean,
component: [Object, Function, String],
content: [String, Array],
programmatic: Boolean,
Expand Down Expand Up @@ -143,15 +138,22 @@ export default {
default: true
}
},
emits: [
'after-enter',
'after-leave',
'cancel',
'close',
'update:modelValue'
],
data() {
return {
isActive: this.active || false,
isActive: this.modelValue || false,
savedScrollTop: null,
newWidth: typeof this.width === 'number'
? this.width + 'px'
: this.width,
animating: !this.active,
destroyed: !this.active
animating: !this.modelValue,
destroyed: !this.modelValue
}
},
computed: {
Expand All @@ -173,7 +175,7 @@ export default {
}
},
watch: {
active(value) {
modelValue(value) {
this.isActive = value
},
isActive(value) {
Expand Down Expand Up @@ -235,7 +237,7 @@ export default {
*/
close() {
this.$emit('close')
this.$emit('update:active', false)
this.$emit('update:modelValue', false)
// Timeout for the animation complete before destroying
if (this.programmatic) {
Expand Down Expand Up @@ -283,14 +285,15 @@ export default {
document.addEventListener('keyup', this.keyPress)
}
},
beforeMount() {
// Insert the Modal component in body tag
// only if it's programmatic
this.programmatic && document.body.appendChild(this.$el)
},
mounted() {
if (this.programmatic) this.isActive = true
else if (this.isActive) this.handleScroll()
if (this.programmatic) {
// Insert the Modal component in body tag
// only if it's programmatic
// the following line used be in `beforeMount`
// but $el is null at `beforeMount`
document.body.appendChild(this.$el)
this.isActive = true
} else if (this.isActive) this.handleScroll()
},
beforeUnmount() {
if (typeof window !== 'undefined') {
Expand Down
56 changes: 39 additions & 17 deletions src/components/modal/index.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
import { createApp, h as createElement } from 'vue'

import Modal from './Modal.vue'

import { VueInstance } from '../../utils/config'
import { merge } from '../../utils/helpers'
import { use, registerComponent, registerComponentProgrammatic } from '../../utils/plugins'

let localVueInstance

const ModalProgrammatic = {
// component specified to the `component` option cannot resolve components
// registered to the caller app, because `open` creates a brand-new app
// by the `createApp` API.
// so the component specified to the `component` option has to explicitly
// reference components that it depends on.
// see /docs/pages/components/modal/examples/ExProgrammatic for an example.
open(params) {
let parent
if (typeof params === 'string') {
params = {
content: params
Expand All @@ -19,7 +23,6 @@ const ModalProgrammatic = {
programmatic: true
}
if (params.parent) {
parent = params.parent
delete params.parent
}
let slot
Expand All @@ -28,24 +31,43 @@ const ModalProgrammatic = {
delete params.content
}
const propsData = merge(defaultParam, params)
const vm = typeof window !== 'undefined' && window.Vue ? window.Vue : localVueInstance || VueInstance
const ModalComponent = vm.extend(Modal)
const component = new ModalComponent({
parent,
el: document.createElement('div'),
propsData
const container = document.createElement('div')
// I could not figure out how to extend an existing app to create a new
// Vue instance on Vue 3.
const vueInstance = createApp({
data() {
return {
modalVNode: null
}
},
methods: {
close() {
const modal =
this.modalVNode?.component?.expose ||
this.modalVNode?.component?.proxy
modal?.close()
}
},
render() {
this.modalVNode = createElement(
Modal,
{
...propsData,
onClose: () => {
vueInstance.unmount()
}
},
slot ? { default: () => slot } : undefined
)
return this.modalVNode
}
})
if (slot) {
component.$slots.default = slot
component.$forceUpdate()
}
return component
return vueInstance.mount(container)
}
}

const Plugin = {
install(Vue) {
localVueInstance = Vue
registerComponent(Vue, Modal)
registerComponentProgrammatic(Vue, 'modal', ModalProgrammatic)
}
Expand Down

0 comments on commit 8b05481

Please sign in to comment.