Merge pull request #3239 from Safelite/feature/CASH-2880
CASH-2880 | More animations
This commit is contained in:
commit
9e1528697c
6 changed files with 209 additions and 85 deletions
|
|
@ -10,22 +10,42 @@
|
||||||
class="date-picker__nav-icon date-picker__nav-icon--flipped"
|
class="date-picker__nav-icon date-picker__nav-icon--flipped"
|
||||||
alt="" />
|
alt="" />
|
||||||
</button>
|
</button>
|
||||||
<div class="date-picker__track d-flex flex-grow-1">
|
<div
|
||||||
<button
|
class="date-picker__track d-flex flex-grow-1"
|
||||||
v-for="date in visibleDates"
|
:style="isLoadingDates ? { '--card-count': windowSize } : null">
|
||||||
:key="date.value"
|
<template v-if="showLoadingPlaceholders">
|
||||||
class="date-picker__day-card d-flex flex-column align-items-center justify-content-center"
|
<div
|
||||||
:class="{
|
v-for="index in windowSize"
|
||||||
'date-picker__day-card--selected': isSelected(date.value),
|
:key="index"
|
||||||
'date-picker__day-card--disabled': !date.isAvailable,
|
class="date-picker__day-card date-picker__day-card--loading d-flex flex-column align-items-center justify-content-center"
|
||||||
}"
|
:style="{ '--card-index': index - 1 }"
|
||||||
:disabled="!date.isAvailable"
|
aria-hidden="true">
|
||||||
:aria-pressed="isSelected(date.value)"
|
<!-- Placeholder text is intentionally non-empty so the spans occupy the
|
||||||
:aria-label="`${date.dayAbbr} ${date.monthAbbr} ${date.day}`"
|
same height as real content. The --loading CSS hides them via
|
||||||
@click="selectDate(date)">
|
visibility:hidden, so they are never visible to users. -->
|
||||||
<span class="date-picker__day-abbr">{{ date.dayAbbr }}</span>
|
<span class="date-picker__day-abbr">MON</span>
|
||||||
<span class="date-picker__day-date">{{ date.monthAbbr }} {{ date.day }}</span>
|
<span class="date-picker__day-date">Jan 00</span>
|
||||||
</button>
|
</div>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<button
|
||||||
|
v-for="(date, index) in visibleDates"
|
||||||
|
:key="date.value"
|
||||||
|
class="date-picker__day-card d-flex flex-column align-items-center justify-content-center"
|
||||||
|
:class="{
|
||||||
|
'date-picker__day-card--selected': isSelected(date.value),
|
||||||
|
'date-picker__day-card--disabled': !date.isAvailable,
|
||||||
|
'date-picker__day-card--loading': isLoadingDates,
|
||||||
|
}"
|
||||||
|
:style="isLoadingDates ? { '--card-index': index } : null"
|
||||||
|
:disabled="!date.isAvailable || isLoadingDates"
|
||||||
|
:aria-pressed="isSelected(date.value)"
|
||||||
|
:aria-label="`${date.dayAbbr} ${date.monthAbbr} ${date.day}`"
|
||||||
|
@click="selectDate(date)">
|
||||||
|
<span class="date-picker__day-abbr">{{ date.dayAbbr }}</span>
|
||||||
|
<span class="date-picker__day-date">{{ date.monthAbbr }} {{ date.day }}</span>
|
||||||
|
</button>
|
||||||
|
</template>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
class="date-picker__nav-btn"
|
class="date-picker__nav-btn"
|
||||||
|
|
@ -110,6 +130,9 @@ export default {
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
showLoadingPlaceholders() {
|
||||||
|
return this.isLoadingDates && !this.allDates.length;
|
||||||
|
},
|
||||||
allDates() {
|
allDates() {
|
||||||
if (this.availableDates === null) return [];
|
if (this.availableDates === null) return [];
|
||||||
|
|
||||||
|
|
@ -308,6 +331,23 @@ export default {
|
||||||
color: $gray-300;
|
color: $gray-300;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&--loading,
|
||||||
|
&--loading.date-picker__day-card--selected,
|
||||||
|
&--loading.date-picker__day-card--disabled {
|
||||||
|
border-color: $gray-200;
|
||||||
|
cursor: default;
|
||||||
|
background-color: $gray-200;
|
||||||
|
background-image: linear-gradient(90deg, $gray-200 25%, $gray-100 50%, $gray-200 75%);
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-size: calc(var(--card-count) * 200%) 100%;
|
||||||
|
animation: date-picker-shimmer 1.5s ease-in-out infinite;
|
||||||
|
|
||||||
|
.date-picker__day-abbr,
|
||||||
|
.date-picker__day-date {
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&__day-abbr,
|
&__day-abbr,
|
||||||
|
|
@ -336,4 +376,13 @@ export default {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@keyframes date-picker-shimmer {
|
||||||
|
0% {
|
||||||
|
background-position-x: calc(var(--card-index) / (var(--card-count) - 1) * 100% + 100%);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
background-position-x: calc(var(--card-index) / (var(--card-count) - 1) * 100% - 100%);
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -91,6 +91,7 @@
|
||||||
import { PREMIUM_TIME_SLOT_ID_FLAG, AppointmentTypeStrings } from "@/constants/schedule-constants";
|
import { PREMIUM_TIME_SLOT_ID_FLAG, AppointmentTypeStrings } from "@/constants/schedule-constants";
|
||||||
import { militaryToTwelveHourTime } from "@/layouts/schedule/helpers/schedule-helper";
|
import { militaryToTwelveHourTime } from "@/layouts/schedule/helpers/schedule-helper";
|
||||||
import schedulingCardLoader from "@/layouts/scheduling/scheduling-card-loader/scheduling-card-loader.vue";
|
import schedulingCardLoader from "@/layouts/scheduling/scheduling-card-loader/scheduling-card-loader.vue";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "mobile-scheduling-card",
|
name: "mobile-scheduling-card",
|
||||||
emits: ["update:modelValue", "zip-code-clicked"],
|
emits: ["update:modelValue", "zip-code-clicked"],
|
||||||
|
|
|
||||||
|
|
@ -63,8 +63,9 @@ function setupMocks() {
|
||||||
|
|
||||||
describe("scheduling.vue", () => {
|
describe("scheduling.vue", () => {
|
||||||
describe("intercept overlay", () => {
|
describe("intercept overlay", () => {
|
||||||
test("does not render interceptOverlay when isLoadingDates is false", () => {
|
test("does not render interceptOverlay when isLoadingDates is false", async () => {
|
||||||
const { wrapper } = setupMocks();
|
const { wrapper } = setupMocks();
|
||||||
|
await wrapper.setData({ isLoadingDates: false });
|
||||||
expect(wrapper.find("intercept-overlay-stub").exists()).toBe(false);
|
expect(wrapper.find("intercept-overlay-stub").exists()).toBe(false);
|
||||||
wrapper.unmount();
|
wrapper.unmount();
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<Form @submit="onSubmit" @invalid-submit="onInvalidSubmit" ref="theForm">
|
<Form @submit="onSubmit" @invalid-submit="onInvalidSubmit" ref="theForm">
|
||||||
<interceptOverlay v-if="isLoadingDates" />
|
<interceptOverlay v-if="isLoadingDates" />
|
||||||
<loadingModal notFullScreen ref="loadingModal" />
|
|
||||||
<funnelHeader cmsWidgetName="FunnelHeaderWidget" ref="funnelHeader" />
|
<funnelHeader cmsWidgetName="FunnelHeaderWidget" ref="funnelHeader" />
|
||||||
<div class="container page-container-grouped-styles">
|
<div class="container page-container-grouped-styles">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
|
|
@ -20,29 +19,35 @@
|
||||||
:availableDates="availableDates"
|
:availableDates="availableDates"
|
||||||
:isLoadingDates="isLoadingDates"
|
:isLoadingDates="isLoadingDates"
|
||||||
@requestMoreDates="handleRequestMoreDates" />
|
@requestMoreDates="handleRequestMoreDates" />
|
||||||
<mobileSchedulingCard
|
<Transition name="card-slide" mode="out-in">
|
||||||
v-if="showMobileSchedulingCard"
|
<div :key="selectedDate">
|
||||||
class="mt-4"
|
<mobileSchedulingCard
|
||||||
v-model="selectedScheduling"
|
v-if="showMobileSchedulingCard"
|
||||||
:providerNumber="mobileProviderAndTimeSlot.providerNumber"
|
class="mt-4"
|
||||||
:timeSlots="mobileTimeSlotsForSelectedDate"
|
v-model="selectedScheduling"
|
||||||
:premiumTimeSlotPrice="premiumTimeSlotPrice"
|
:providerNumber="mobileProviderAndTimeSlot.providerNumber"
|
||||||
:zipCode="serviceZipCode"
|
:timeSlots="mobileTimeSlotsForSelectedDate"
|
||||||
:showFreeFlag="showMobileFreeFlag"
|
:premiumTimeSlotPrice="premiumTimeSlotPrice"
|
||||||
:radioGroupName="schedulingRadioGroupName"
|
:zipCode="serviceZipCode"
|
||||||
:isLoading="isLoadingDates"
|
:showFreeFlag="showMobileFreeFlag"
|
||||||
@zip-code-clicked="onMobileZipCodeClicked" />
|
:radioGroupName="schedulingRadioGroupName"
|
||||||
<inshopSchedulingCard
|
:isLoading="isLoadingDates"
|
||||||
v-for="{ provider } in inShopProvidersAndTimeslots"
|
@zip-code-clicked="onMobileZipCodeClicked" />
|
||||||
v-show="selectedDate"
|
<inshopSchedulingCard
|
||||||
:key="provider.providerNumber"
|
v-for="{ provider } in inShopProvidersAndTimeslots"
|
||||||
class="mt-4"
|
v-show="showInshopSchedulingCards"
|
||||||
v-model="selectedScheduling"
|
:key="provider.providerNumber"
|
||||||
:provider="provider"
|
class="mt-4"
|
||||||
:timeSlots="getInshopTimeSlotsForSelectedDate(provider.providerNumber)"
|
v-model="selectedScheduling"
|
||||||
:radioGroupName="schedulingRadioGroupName"
|
:provider="provider"
|
||||||
:isLoading="isLoadingDates"
|
:timeSlots="
|
||||||
@address-clicked="onInshopAddressClicked(provider)" />
|
getInshopTimeSlotsForSelectedDate(provider.providerNumber)
|
||||||
|
"
|
||||||
|
:radioGroupName="schedulingRadioGroupName"
|
||||||
|
:isLoading="isLoadingDates"
|
||||||
|
@address-clicked="onInshopAddressClicked(provider)" />
|
||||||
|
</div>
|
||||||
|
</Transition>
|
||||||
<navbar
|
<navbar
|
||||||
cmsWidgetName="FunnelFooterWidget"
|
cmsWidgetName="FunnelFooterWidget"
|
||||||
ref="navbar"
|
ref="navbar"
|
||||||
|
|
@ -62,7 +67,6 @@ import { Form } from "vee-validate";
|
||||||
import datePicker from "@/layouts/scheduling/date-picker/date-picker";
|
import datePicker from "@/layouts/scheduling/date-picker/date-picker";
|
||||||
import mobileSchedulingCard from "@/layouts/scheduling/mobile-scheduling-card/mobile-scheduling-card";
|
import mobileSchedulingCard from "@/layouts/scheduling/mobile-scheduling-card/mobile-scheduling-card";
|
||||||
import inshopSchedulingCard from "@/layouts/scheduling/inshop-scheduling-card/inshop-scheduling-card";
|
import inshopSchedulingCard from "@/layouts/scheduling/inshop-scheduling-card/inshop-scheduling-card";
|
||||||
import loadingModal from "@/fmg-components/loading-modal/loading-modal.vue";
|
|
||||||
import interceptOverlay from "@/ux-components/intercept-overlay/intercept-overlay";
|
import interceptOverlay from "@/ux-components/intercept-overlay/intercept-overlay";
|
||||||
import store from "@/store";
|
import store from "@/store";
|
||||||
import { AppointmentTypeStrings, PREMIUM_FEE_PART_TYPE } from "@/constants/schedule-constants";
|
import { AppointmentTypeStrings, PREMIUM_FEE_PART_TYPE } from "@/constants/schedule-constants";
|
||||||
|
|
@ -138,17 +142,6 @@ export default {
|
||||||
async beforeRouteEnter(to, from, next) {
|
async beforeRouteEnter(to, from, next) {
|
||||||
const serviceZipCode = store.getters.order.serviceLocation.zipCode;
|
const serviceZipCode = store.getters.order.serviceLocation.zipCode;
|
||||||
|
|
||||||
const shopProviderData = await store.dispatch("getProviders", {
|
|
||||||
payload: { serviceZipCode },
|
|
||||||
pageNameToLog: to.name,
|
|
||||||
});
|
|
||||||
// Get the first 3 providers from the shopProviderData
|
|
||||||
const providers = shopProviderData?.data?.shopProviders?.slice(0, 3) ?? [];
|
|
||||||
const mobileProviderNumber = shopProviderData?.data?.mobileProviderNumber ?? null;
|
|
||||||
|
|
||||||
const startDate = toDateString(0);
|
|
||||||
const endDate = toDateString(SCHEDULE_FETCH_DAYS - 1);
|
|
||||||
|
|
||||||
const promiseResultMap = [
|
const promiseResultMap = [
|
||||||
{
|
{
|
||||||
resultKey: "cmsContent",
|
resultKey: "cmsContent",
|
||||||
|
|
@ -160,45 +153,64 @@ export default {
|
||||||
pageNameToLog: to.name,
|
pageNameToLog: to.name,
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
...providers.map((provider, i) => ({
|
{
|
||||||
resultKey: `inshopTimeSlots_${i}`,
|
resultKey: "providers",
|
||||||
promise: fetchInshopTimeSlots({
|
promise: store.dispatch("getProviders", {
|
||||||
startDate,
|
payload: { serviceZipCode },
|
||||||
endDate,
|
|
||||||
providerNumber: provider.providerNumber,
|
|
||||||
pageNameToLog: to.name,
|
pageNameToLog: to.name,
|
||||||
}),
|
}),
|
||||||
})),
|
},
|
||||||
// Get the mobile time slots if a mobile provider number is available
|
|
||||||
...(mobileProviderNumber
|
|
||||||
? [
|
|
||||||
{
|
|
||||||
resultKey: "mobileTimeSlots",
|
|
||||||
promise: fetchMobileTimeSlots({
|
|
||||||
startDate,
|
|
||||||
endDate,
|
|
||||||
zipCode: serviceZipCode,
|
|
||||||
pageNameToLog: to.name,
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
]
|
|
||||||
: []),
|
|
||||||
];
|
];
|
||||||
const resultMap = await settleAllPromises(promiseResultMap);
|
const resultMap = await settleAllPromises(promiseResultMap);
|
||||||
next((vm) => {
|
// Get the first 3 providers from the shopProviderData
|
||||||
|
const providers = resultMap.providers?.shopProviders?.slice(0, 3) ?? [];
|
||||||
|
const mobileProviderNumber = resultMap.providers?.mobileProviderNumber ?? null;
|
||||||
|
next(async (vm) => {
|
||||||
vm.setCmsContent(resultMap.cmsContent);
|
vm.setCmsContent(resultMap.cmsContent);
|
||||||
vm.inShopProvidersAndTimeslots = providers.map((provider, i) => ({
|
vm.inShopProvidersAndTimeslots = providers.map((provider) => ({
|
||||||
provider,
|
provider,
|
||||||
timeSlots: resultMap[`inshopTimeSlots_${i}`] ?? null,
|
timeSlots: null,
|
||||||
}));
|
}));
|
||||||
vm.mobileProviderAndTimeSlot = mobileProviderNumber
|
vm.mobileProviderAndTimeSlot = mobileProviderNumber
|
||||||
? {
|
? { providerNumber: mobileProviderNumber, timeSlots: null }
|
||||||
providerNumber: mobileProviderNumber,
|
|
||||||
timeSlots: resultMap.mobileTimeSlots ?? null,
|
|
||||||
}
|
|
||||||
: null;
|
: null;
|
||||||
|
const startDate = toDateString(0);
|
||||||
|
const endDate = toDateString(SCHEDULE_FETCH_DAYS - 1);
|
||||||
|
const timeSlotsPromiseResultMap = [
|
||||||
|
...providers.map((provider, i) => ({
|
||||||
|
resultKey: `inshopTimeSlots_${i}`,
|
||||||
|
promise: fetchInshopTimeSlots({
|
||||||
|
startDate,
|
||||||
|
endDate,
|
||||||
|
providerNumber: provider.providerNumber,
|
||||||
|
pageNameToLog: to.name,
|
||||||
|
}),
|
||||||
|
})),
|
||||||
|
// Get the mobile time slots if a mobile provider number is available
|
||||||
|
...(mobileProviderNumber
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
resultKey: "mobileTimeSlots",
|
||||||
|
promise: fetchMobileTimeSlots({
|
||||||
|
startDate,
|
||||||
|
endDate,
|
||||||
|
zipCode: serviceZipCode,
|
||||||
|
pageNameToLog: to.name,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
: []),
|
||||||
|
];
|
||||||
|
const timeSlotsResultMap = await settleAllPromises(timeSlotsPromiseResultMap);
|
||||||
|
vm.inShopProvidersAndTimeslots.forEach((entry, i) => {
|
||||||
|
entry.timeSlots = timeSlotsResultMap[`inshopTimeSlots_${i}`] ?? null;
|
||||||
|
});
|
||||||
|
if (vm.mobileProviderAndTimeSlot) {
|
||||||
|
vm.mobileProviderAndTimeSlot.timeSlots = timeSlotsResultMap.mobileTimeSlots ?? null;
|
||||||
|
}
|
||||||
vm.datesLoaded = true;
|
vm.datesLoaded = true;
|
||||||
vm.mobilePremiumAppointmentFee = resultMap.mobilePremiumFee ?? null;
|
vm.mobilePremiumAppointmentFee = resultMap.mobilePremiumFee ?? null;
|
||||||
|
vm.isLoadingDates = false;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
|
|
@ -217,7 +229,12 @@ export default {
|
||||||
return store.getters.order.serviceLocation.zipCode;
|
return store.getters.order.serviceLocation.zipCode;
|
||||||
},
|
},
|
||||||
showMobileSchedulingCard() {
|
showMobileSchedulingCard() {
|
||||||
return Boolean(this.mobileProviderAndTimeSlot && this.selectedDate);
|
return Boolean(
|
||||||
|
this.mobileProviderAndTimeSlot && (this.selectedDate || this.isLoadingDates)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
showInshopSchedulingCards() {
|
||||||
|
return this.selectedDate || this.isLoadingDates;
|
||||||
},
|
},
|
||||||
mobileTimeSlotsForSelectedDate() {
|
mobileTimeSlotsForSelectedDate() {
|
||||||
if (!this.selectedDate) {
|
if (!this.selectedDate) {
|
||||||
|
|
@ -251,7 +268,7 @@ export default {
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
selectedDate: null,
|
selectedDate: null,
|
||||||
isLoadingDates: false,
|
isLoadingDates: true,
|
||||||
datesLoaded: false,
|
datesLoaded: false,
|
||||||
datePickerStartDate: toDateString(0),
|
datePickerStartDate: toDateString(0),
|
||||||
datePickerEndDate: toDateString(SCHEDULE_FETCH_DAYS - 1),
|
datePickerEndDate: toDateString(SCHEDULE_FETCH_DAYS - 1),
|
||||||
|
|
@ -378,7 +395,6 @@ export default {
|
||||||
datePicker,
|
datePicker,
|
||||||
mobileSchedulingCard,
|
mobileSchedulingCard,
|
||||||
inshopSchedulingCard,
|
inshopSchedulingCard,
|
||||||
loadingModal,
|
|
||||||
interceptOverlay,
|
interceptOverlay,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
@ -391,4 +407,20 @@ h5 {
|
||||||
line-height: 32px;
|
line-height: 32px;
|
||||||
font-size: 1.25rem;
|
font-size: 1.25rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.card-slide-enter-active {
|
||||||
|
transition:
|
||||||
|
opacity 0.3s ease-out,
|
||||||
|
transform 0.3s ease-out;
|
||||||
|
}
|
||||||
|
.card-slide-leave-active {
|
||||||
|
transition: opacity 0.32s ease-in;
|
||||||
|
}
|
||||||
|
.card-slide-enter-from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateX(24px);
|
||||||
|
}
|
||||||
|
.card-slide-leave-to {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -14,4 +14,33 @@ describe("intercept-overlay.vue", () => {
|
||||||
expect(wrapper.text()).toBe("");
|
expect(wrapper.text()).toBe("");
|
||||||
wrapper.unmount();
|
wrapper.unmount();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("keyboard interception", () => {
|
||||||
|
test("adds a capturing keydown listener on mount and removes it on unmount", () => {
|
||||||
|
const addSpy = jest.spyOn(document, "addEventListener");
|
||||||
|
const removeSpy = jest.spyOn(document, "removeEventListener");
|
||||||
|
|
||||||
|
const wrapper = shallowMount(interceptOverlay);
|
||||||
|
expect(addSpy).toHaveBeenCalledWith("keydown", expect.any(Function), true);
|
||||||
|
|
||||||
|
const [, handler] = addSpy.mock.calls.find(
|
||||||
|
([type, , capture]) => type === "keydown" && capture === true
|
||||||
|
);
|
||||||
|
|
||||||
|
wrapper.unmount();
|
||||||
|
expect(removeSpy).toHaveBeenCalledWith("keydown", handler, true);
|
||||||
|
|
||||||
|
addSpy.mockRestore();
|
||||||
|
removeSpy.mockRestore();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("blockKey prevents default and stops immediate propagation", () => {
|
||||||
|
const wrapper = shallowMount(interceptOverlay);
|
||||||
|
const event = { preventDefault: jest.fn(), stopImmediatePropagation: jest.fn() };
|
||||||
|
wrapper.vm.blockKey(event);
|
||||||
|
expect(event.preventDefault).toHaveBeenCalled();
|
||||||
|
expect(event.stopImmediatePropagation).toHaveBeenCalled();
|
||||||
|
wrapper.unmount();
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,22 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="intercept-overlay" aria-hidden="true"></div>
|
<div class="intercept-overlay" aria-hidden="true" @keydown.capture="blockKey"></div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
name: "intercept-overlay",
|
name: "intercept-overlay",
|
||||||
|
mounted() {
|
||||||
|
document.addEventListener("keydown", this.blockKey, true);
|
||||||
|
},
|
||||||
|
beforeUnmount() {
|
||||||
|
document.removeEventListener("keydown", this.blockKey, true);
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
blockKey(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopImmediatePropagation();
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue