Skip to content

Commit

Permalink
Merge pull request #1499 from empathyco/feature/migrate-base-events-m…
Browse files Browse the repository at this point in the history
…odal-component-to-composition-api

feat: migrate BaseEventsModal to composition API
  • Loading branch information
victorcg88 authored Jun 4, 2024
2 parents 89bdd5a + 870a02d commit b138baa
Show file tree
Hide file tree
Showing 4 changed files with 156 additions and 85 deletions.
1 change: 1 addition & 0 deletions packages/_vue3-migration-test/src/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,5 @@ export { default as TestHighlight } from './test-highlight.vue';
export { default as TestBaseResultImages } from './result/test-base-result-images.vue';
export { default as TestBasePanel } from './panels/test-base-panel.vue';
export { default as TestBaseKeyboardNavigation } from './test-base-keyboard-navigation.vue';
export { default as TestBaseEventsModal } from './modals/test-base-events-modal.vue';
export { default as TestBaseIdModal } from './modals/test-base-id-modal.vue';
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<template>
<div class="base-modal">
<button @click="openModal">Open modal</button>
<BaseEventsModal
:animation="Fade"
:focusOnOpen="true"
v-bind="$attrs"
contentClass="content"
overlayClass="overlay"
>
<h1>Hello</h1>
<p>The modal is working with events!!</p>
<button @click="closeModal">Close modal</button>
</BaseEventsModal>
</div>
</template>

<script setup lang="ts">
import BaseEventsModal from '../../../../x-components/src/components/modals/base-events-modal.vue';
import Fade from '../../../../x-components/src/components/animations/fade.vue';
import { use$x } from '../../../../x-components/src/composables/use-$x';
const _$x = use$x();
const openModal = () => _$x.emit('UserClickedOpenEventsModal');
const closeModal = () => _$x.emit('UserClickedCloseEventsModal');
</script>

<style>
.base-modal {
.content {
background: white;
margin: auto;
width: 50%;
border: 3px solid green;
padding: 10px;
}
.overlay {
background-color: #00ff80;
}
}
</style>
6 changes: 6 additions & 0 deletions packages/_vue3-migration-test/src/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import {
TestBaseResultImages,
TestBasePanel,
TestBaseKeyboardNavigation,
TestBaseEventsModal,
TestBaseIdModal
} from './';

Expand Down Expand Up @@ -166,6 +167,11 @@ const routes = [
name: 'TestBaseKeyboardNavigation',
component: TestBaseKeyboardNavigation
},
{
path: '/base-events-modal',
name: 'BaseEventsModal',
component: TestBaseEventsModal
},
{
path: '/test-base-id-modal',
name: 'TestBaseIdModal',
Expand Down
191 changes: 106 additions & 85 deletions packages/x-components/src/components/modals/base-events-modal.vue
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
<template>
<BaseModal
v-bind="$attrs"
ref="baseModalEl"
@click:overlay="emitBodyClickEvent"
@focusin:body="emitBodyClickEvent"
:animation="animation"
:open="isOpen"
v-bind="$attrs"
>
<slot />
</BaseModal>
</template>

<script lang="ts">
import Vue from 'vue';
import { Component, Prop } from 'vue-property-decorator';
import { defineComponent, PropType, ref } from 'vue';
import { XEvent } from '../../wiring/events.types';
import { XOn } from '../decorators/bus.decorators';
import { WireMetadata } from '../../wiring/wiring.types';
import { getTargetElement, isElementEqualOrContained } from '../../utils/html';
import { AnimationProp } from '../../types/animation-prop';
import { use$x } from '../../composables/use-$x';
import BaseModal from './base-modal.vue';
/**
Expand All @@ -30,88 +30,109 @@
*
* @public
*/
@Component({
components: { BaseModal }
})
export default class BaseEventsModal extends Vue {
/**
* Animation to use for opening/closing the modal.
*/
@Prop()
public animation?: Vue | string;
/**
* Array of {@link XEvent} to listen to open the modal.
*/
@Prop({ default: (): XEvent[] => ['UserClickedOpenEventsModal'] })
public eventsToOpenModal!: XEvent[];
/**
* Array of {@link XEvent} to listen to close the modal.
*/
@Prop({
default: (): XEvent[] => ['UserClickedCloseEventsModal', 'UserClickedOutOfEventsModal']
})
public eventsToCloseModal!: XEvent[];
/**
* Event to emit when any part of the website out of the modal has been clicked.
*/
@Prop({ default: 'UserClickedOutOfEventsModal' })
public bodyClickEvent!: XEvent;
/**
* Whether the modal is open or not.
*/
protected isOpen = false;
/** The element that opened the modal. */
protected openerElement?: HTMLElement;
/**
* Opens the modal.
*
* @param _payload - The payload of the event that opened the modal.
* @param metadata - The metadata of the event that opened the modal.
*
* @internal
*/
@XOn(component => (component as BaseEventsModal).eventsToOpenModal)
openModal(_payload: unknown, metadata: WireMetadata): void {
if (!this.isOpen) {
this.openerElement = metadata.target;
this.isOpen = true;
}
}
/**
* Closes the modal.
*
* @internal
*/
@XOn(component => (component as BaseEventsModal).eventsToCloseModal)
closeModal(): void {
if (this.isOpen) {
this.isOpen = false;
}
}
/**
* Emits the event defined in the {@link BaseEventsModal.bodyClickEvent} event unless the passed
* event target is the button that opened the modal.
*
* @param event - The event that triggered the close attempt.
* @public
*/
protected emitBodyClickEvent(event: MouseEvent | FocusEvent): void {
// Prevents clicking the open button when the panel is already open to close the panel.
if (
!this.openerElement ||
!isElementEqualOrContained(this.openerElement, getTargetElement(event))
) {
this.$x.emit(this.bodyClickEvent, undefined, { target: this.$el as HTMLElement });
export default defineComponent({
name: 'BaseEventsModal',
components: {
BaseModal
},
props: {
/**
* Animation to use for opening/closing the modal.
*/
animation: AnimationProp,
/**
* Array of {@link XEvent} to listen to open the modal.
*/
eventsToOpenModal: {
type: Array as PropType<XEvent[]>,
default: (): XEvent[] => ['UserClickedOpenEventsModal']
},
/**
* Array of {@link XEvent} to listen to close the modal.
*/
eventsToCloseModal: {
type: Array as PropType<XEvent[]>,
default: (): XEvent[] => ['UserClickedCloseEventsModal', 'UserClickedOutOfEventsModal']
},
/**
* Event to emit when any part of the website out of the modal has been clicked.
*/
bodyClickEvent: {
type: String as PropType<XEvent>,
default: 'UserClickedOutOfEventsModal'
}
},
setup(props) {
const $x = use$x();
/**
* Whether the modal is open or not.
*/
const isOpen = ref(false);
/** The element that opened the modal. */
const openerElement = ref<HTMLElement>();
const baseModalEl = ref<HTMLElement>();
/**
* Opens the modal.
*
* @param _payload - The payload of the event that opened the modal.
* @param metadata - The metadata of the event that opened the modal.
*
* @internal
*/
props.eventsToOpenModal?.forEach(event =>
$x.on(event, true).subscribe(({ metadata }) => {
if (!isOpen.value) {
openerElement.value = metadata.target;
isOpen.value = true;
}
})
);
/**
* Closes the modal.
*
* @internal
*/
props.eventsToCloseModal?.forEach(event =>
$x.on(event, false).subscribe(() => {
if (isOpen.value) {
isOpen.value = false;
}
})
);
/**
* Emits the event defined in the {@link BaseEventsModal.bodyClickEvent} event unless the passed
* event target is the button that opened the modal.
*
* @param event - The event that triggered the close attempt.
* @public
*/
const emitBodyClickEvent = (event: MouseEvent | FocusEvent) => {
// Prevents clicking the open button when the panel is already open to close the panel.
if (
!openerElement.value ||
!isElementEqualOrContained(openerElement.value, getTargetElement(event))
) {
$x.emit(props.bodyClickEvent, undefined, { target: baseModalEl.value as HTMLElement });
}
};
return {
isOpen,
openerElement,
baseModalEl,
emitBodyClickEvent
};
}
}
});
</script>

<docs lang="mdx">
Expand Down

0 comments on commit b138baa

Please sign in to comment.