Skip to content

Commit fb975e3

Browse files
authored
Merge pull request #254 from php-school/focus-trapping
Focus trapping in dialog/modals + some fixes for confirm dialog
2 parents 5f538e7 + 594eff1 commit fb975e3

15 files changed

+122
-58
lines changed

assets/cloud.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import WorkshopExerciseSelectionList from './components/WorkshopExerciseSelectio
1111
import PassNotification from "./components/PassNotification.vue";
1212
import ExerciseVerify from "./components/ExerciseVerify.vue";
1313
import ExerciseEditor from "./components/ExerciseEditor.vue";
14+
import { FocusTrap } from 'focus-trap-vue'
1415
import VueClickAway from "vue3-click-away";
1516
import VueDiff from 'vue-diff';
1617
import 'vue-diff/dist/index.css';
@@ -42,6 +43,8 @@ Object.entries(results).forEach(([name, resultComponent]) => {
4243
app.component(name, resultComponent);
4344
});
4445

46+
app.component('FocusTrap', FocusTrap)
47+
4548
app.config.unwrapInjectedRef = true;
4649

4750
app.use(VueClickAway);

assets/components/Confirm.vue

Lines changed: 37 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ export default {
66
ExclamationTriangleIcon,
77
},
88
data: () => ({
9+
focusActive: false,
910
isOpen: false,
1011
title: null,
1112
message: null,
@@ -20,10 +21,13 @@ export default {
2021
this.isOpen = true;
2122
},
2223
close() {
24+
document.removeEventListener('keyup', this.$el.escapeEventHandler);
25+
2326
this.isOpen = false;
27+
this.focusActive = false;
28+
this.showCancel = true;
2429
},
2530
show(options = {}) {
26-
document.removeEventListener('keyup', this.$el.escapeEventHandler);
2731
2832
this.title = options.title;
2933
this.message = options.message;
@@ -35,19 +39,29 @@ export default {
3539
}
3640
3741
if (options.disableCancel) {
38-
this.showCancel = false;
42+
this.showCancel = false;
43+
this.$el.escapeEventHandler = (evt) => {
44+
if (evt.code === 'Escape') {
45+
this.confirm();
46+
}
47+
};
3948
} else {
4049
//hitting escape is the same as cancelling
4150
this.$el.escapeEventHandler = (evt) => {
4251
if (evt.code === 'Escape') {
4352
this.decline();
4453
}
4554
};
46-
47-
document.addEventListener('keyup', this.$el.escapeEventHandler);
4855
}
4956
57+
document.addEventListener('keyup', this.$el.escapeEventHandler);
58+
5059
this.open();
60+
61+
this.$nextTick(() => {
62+
this.focusActive = true;
63+
});
64+
5165
return new Promise((resolve,reject) => {
5266
this.resolvePromise = resolve;
5367
this.rejectPromise = reject;
@@ -66,29 +80,31 @@ export default {
6680
</script>
6781

6882
<template>
69-
<div v-show="isOpen" class="fixed rounded-lg bg-gray-800 flex flex-col justify-start w-full h-full inset-0 z-40 bg-opacity-80">
70-
<div class="bg-grey-800 h-full w-full flex justify-center items-center drop-shadow-xl">
71-
<div class="bg-gray-900 max-w-sm max-h-[calc(1/2*100%)] rounded-lg">
72-
<div class="p-4 rounded-t flex-none flex justify-between items-top border-b border-solid border-slate-600">
73-
<div class="flex items-center">
74-
<ExclamationTriangleIcon class="h-6 w-6 text-pink-500 mr-2" />
75-
<h3 class="text-base font-semibold lg:text-lg text-white pt-0 mt-0">{{ title }}</h3>
83+
<focus-trap v-model:active="focusActive" :initial-focus="() => $refs.confirm">
84+
<div v-show="isOpen" class="fixed rounded-lg bg-gray-800 flex flex-col justify-start w-full h-full inset-0 z-40 bg-opacity-80">
85+
<div class="bg-grey-800 h-full w-full flex justify-center items-center drop-shadow-xl">
86+
<div class="bg-gray-900 max-w-sm max-h-[calc(1/2*100%)] rounded-lg">
87+
<div class="p-4 rounded-t flex-none flex justify-between items-top border-b border-solid border-slate-600">
88+
<div class="flex items-center">
89+
<ExclamationTriangleIcon class="h-6 w-6 text-pink-500 mr-2" />
90+
<h3 class="text-base font-semibold lg:text-lg text-white pt-0 mt-0">{{ title }}</h3>
91+
</div>
7692
</div>
77-
</div>
78-
<div class="p-4 rounded-t flex-none flex justify-between items-top border-b border-solid border-slate-600">
79-
<div class="flex items-center">
80-
<p class="text-white">{{ message }}</p>
93+
<div class="p-4 rounded-t flex-none flex justify-between items-top border-b border-solid border-slate-600">
94+
<div class="flex items-center">
95+
<p class="text-white">{{ message }}</p>
96+
</div>
8197
</div>
82-
</div>
83-
<div class="p-6 rounded-b border-solid border-slate-600 flex-none">
84-
<div class="flex justify-end">
85-
<button v-if="showCancel" @click="decline" type="button" class="inline-flex items-center w-full justify-center rounded-full border border-pink-600 px-8 py-2 text-base font-medium text-gray-400 hover:text-white shadow-sm hover:bg-pink-600 focus:outline-none sm:ml-3 sm:w-auto sm:text-sm">{{ cancelMessage }}</button>
86-
<button @click="confirm" type="button" class="inline-flex items-center w-full justify-center rounded-full border border-transparent bg-pink-600 px-8 py-2 text-base font-medium text-white shadow-sm hover:bg-pink-700 focus:outline-none sm:ml-3 sm:w-auto sm:text-sm">{{ okMessage }}</button>
98+
<div class="p-6 rounded-b border-solid border-slate-600 flex-none">
99+
<div class="flex justify-end">
100+
<button v-if="showCancel" @click="decline" type="button" class="inline-flex items-center w-full justify-center rounded-full border border-pink-600 px-8 py-2 text-base font-medium text-gray-400 hover:text-white shadow-sm hover:bg-pink-600 focus:outline-none sm:ml-3 sm:w-auto sm:text-sm focus:outline-none focus:ring focus:ring-pink-800">{{ cancelMessage }}</button>
101+
<button ref="confirm" @click="confirm" type="button" class="inline-flex items-center w-full justify-center rounded-full border border-transparent bg-pink-600 px-8 py-2 text-base font-medium text-white shadow-sm hover:bg-pink-700 focus:outline-none sm:ml-3 sm:w-auto sm:text-sm focus:outline-none focus:ring focus:ring-pink-800">{{ okMessage }}</button>
102+
</div>
87103
</div>
88104
</div>
89105
</div>
90106
</div>
91-
</div>
107+
</focus-trap>
92108
</template>
93109

94110

assets/components/ExerciseEditor.vue

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,6 @@ export default {
128128
return this.studentState.completedExercises.includes(this.exercise.name);
129129
},
130130
},
131-
132131
methods: {
133132
saveSolution(file) {
134133
localStorage.setItem(this.currentExercise.workshop.code + '.' + this.currentExercise.exercise.slug + '.' + file.name, file.content);
@@ -489,7 +488,7 @@ export default {
489488
<package-search ref="packageSearch" @package-selected="packageSelected"
490489
v-model="newDependency" class="w-full"></package-search>
491490
<button :disabled="newDependency === ''" @click.stop="addDependency" type="button"
492-
class="inline-flex items-center h-9 justify-center rounded-full border border-transparent w-16 bg-pink-600 px-4 py-2 text-base font-medium text-white shadow-sm hover:bg-pink-700 focus:outline-none sm:ml-3 sm:text-sm disabled:opacity-70 disabled:hover:bg-pink-600">
491+
class="inline-flex items-center h-9 justify-center rounded-full border border-transparent w-16 bg-pink-600 px-4 py-2 text-base font-medium text-white shadow-sm hover:bg-pink-700 focus:outline-none focus:ring focus:ring-pink-800 sm:ml-3 sm:text-sm disabled:opacity-70 disabled:hover:bg-pink-600">
493492
<ArrowPathIcon v-cloak v-show="loadingComposerAdd" class="w-4 h-4 animate-spin"/>
494493
<span v-if="!loadingComposerAdd">Add</span>
495494
</button>
@@ -510,7 +509,7 @@ export default {
510509
<template #footer>
511510
<div class="flex justify-end">
512511
<button @click="openComposerModal = false" type="button"
513-
class="inline-flex items-center w-full justify-center rounded-full border border-transparent bg-pink-600 px-8 py-2 text-base font-medium text-white shadow-sm hover:bg-pink-700 focus:outline-none sm:ml-3 sm:w-auto sm:text-sm">
512+
class="inline-flex items-center w-full justify-center rounded-full border border-transparent bg-pink-600 px-8 py-2 text-base font-medium text-white shadow-sm hover:bg-pink-700 focus:outline-none focus:ring focus:ring-pink-800 sm:ml-3 sm:w-auto sm:text-sm">
514513
Close
515514
</button>
516515
</div>
@@ -543,7 +542,7 @@ export default {
543542
<template #footer>
544543
<div class="flex justify-end">
545544
<button id="lets-go" @click="openProblemModal = false" type="button"
546-
class="inline-flex items-center w-full justify-center rounded-full border border-transparent bg-pink-600 px-8 py-2 text-base font-medium text-white shadow-sm hover:bg-pink-700 focus:outline-none sm:ml-3 sm:w-auto sm:text-sm">
545+
class="inline-flex items-center w-full justify-center rounded-full border border-transparent bg-pink-600 px-8 py-2 text-base font-medium text-white shadow-sm hover:bg-pink-700 focus:outline-none focus:ring focus:ring-pink-800 sm:ml-3 sm:w-auto sm:text-sm">
547546
Let's go!
548547
</button>
549548
</div>

assets/components/ExerciseVerify.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,7 @@ export default {
209209
</template>
210210
<template #footer>
211211
<div class="flex justify-end">
212-
<button type="button" class="inline-flex items-center w-full justify-center rounded-full border border-transparent bg-pink-600 px-8 py-2 text-base font-medium text-white shadow-sm hover:bg-pink-700 focus:outline-none focus:ring-2 focus:ring-green-500 focus:ring-offset-2 sm:ml-3 sm:w-auto sm:text-sm" @click="runSolution">
212+
<button type="button" class="inline-flex items-center w-full justify-center rounded-full border border-transparent bg-pink-600 px-8 py-2 text-base font-medium text-white shadow-sm hover:bg-pink-700 focus:outline-none focus:ring focus:ring-pink-800 sm:ml-3 sm:w-auto sm:text-sm" @click="runSolution">
213213
<ArrowPathIcon :class="{ 'animate-spin': loadingRun}" class="w-4 h-4 mr-2 -ml-1"/>
214214
Run again
215215
</button>

assets/components/Modal.vue

Lines changed: 25 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,8 @@ export default {
5050
'5xl': 'max-w-5xl',
5151
'6xl': 'max-w-6xl',
5252
'7xl': 'max-w-7xl',
53-
}
53+
},
54+
focusActive: false,
5455
}
5556
},
5657
methods: {
@@ -74,31 +75,31 @@ export default {
7475
</script>
7576

7677
<template >
77-
<div class="">
78-
<div class="bg-gray-900 bg-opacity-80 fixed inset-0 z-40"/>
79-
<div tabindex="-1"
80-
class="overflow-x-hidden fixed top-0 right-0 left-0 z-50 w-full md:inset-0 md:h-full justify-center items-center flex"
81-
>
82-
<div :id="id" v-click-away="clickAway" class="relative rounded-lg shadow bg-gray-800 flex flex-col justify-start w-full modal-border border-2 border-solid border-transparent" :class="[modalSizeClasses[size], maxHeight]">
83-
<div class="p-6 rounded-t flex-none flex justify-between items-top"
84-
:class="$slots.header ? 'border-b border-solid border-slate-600' : ''">
85-
<slot name="header"/>
86-
<div>
87-
<button :id="id + '-close'" @click="closeModal($event)" type="button"
88-
class="text-gray-400 bg-transparent rounded-lg text-sm p-1.5 ml-auto inline-flex items-center hover:bg-gray-600 hover:text-white">
89-
<XMarkIcon class="w-5 h-5"/>
90-
</button>
78+
<focus-trap :v-model:active="true">
79+
<div class="">
80+
<div class="bg-gray-900 bg-opacity-80 fixed inset-0 z-40"/>
81+
<div tabindex="-1" class="overflow-x-hidden fixed top-0 right-0 left-0 z-50 w-full md:inset-0 md:h-full justify-center items-center flex">
82+
<div :id="id" v-click-away="clickAway" class="relative rounded-lg shadow bg-gray-800 flex flex-col justify-start w-full modal-border border-2 border-solid border-transparent" :class="[modalSizeClasses[size], maxHeight]">
83+
<div class="p-6 rounded-t flex-none flex justify-between items-top"
84+
:class="$slots.header ? 'border-b border-solid border-slate-600' : ''">
85+
<slot name="header"/>
86+
<div>
87+
<button :id="id + '-close'" @click="closeModal($event)" type="button"
88+
class="text-gray-400 bg-transparent rounded-lg text-sm p-1.5 ml-auto inline-flex items-center hover:bg-gray-600 hover:text-white focus:outline-none focus:ring focus:ring-pink-800">
89+
<XMarkIcon class="w-5 h-5"/>
90+
</button>
91+
</div>
92+
</div>
93+
<div class="p-6 flex-1" :class="{'pt-0' : !$slots.header, 'overflow-y-auto scrollbar-thin scrollbar-thumb-pink-500 scrollbar-track-none scrollbar-thumb-rounded-full scrollbar-track-rounded-full mr-[3px]' : scrollContent}" >
94+
<slot name="body"/>
95+
</div>
96+
<div v-if="$slots.footer" class="p-6 rounded-b border-t border-solid border-slate-600 flex-none">
97+
<slot name="footer"/>
98+
</div>
9199
</div>
92-
</div>
93-
<div class="p-6 flex-1" :class="{'pt-0' : !$slots.header, 'overflow-y-auto scrollbar-thin scrollbar-thumb-pink-500 scrollbar-track-none scrollbar-thumb-rounded-full scrollbar-track-rounded-full mr-[3px]' : scrollContent}" >
94-
<slot name="body"/>
95-
</div>
96-
<div v-if="$slots.footer" class="p-6 rounded-b border-t border-solid border-slate-600 flex-none">
97-
<slot name="footer"/>
98-
</div>
99100
</div>
100-
</div>
101-
</div>
101+
</div>
102+
</focus-trap>
102103
</template>
103104

104105
<style>

assets/components/results/CgiOutputMismatch.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ export default {
9898

9999
<template #footer>
100100
<div class="flex justify-end">
101-
<button @click="openModal = false" type="button" class="inline-flex items-center w-full justify-center rounded-full border border-transparent bg-pink-600 px-8 py-2 text-base font-medium text-white shadow-sm hover:bg-pink-700 focus:outline-none sm:ml-3 sm:w-auto sm:text-sm">
101+
<button @click="openModal = false" type="button" class="inline-flex items-center w-full justify-center rounded-full border border-transparent bg-pink-600 px-8 py-2 text-base font-medium text-white shadow-sm hover:bg-pink-700 focus:outline-none focus:ring focus:ring-pink-800 sm:ml-3 sm:w-auto sm:text-sm">
102102
Close
103103
</button>
104104
</div>

assets/components/results/CgiRun.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ export default {
8686

8787
<template #footer>
8888
<div class="flex justify-end">
89-
<button @click="openModal = false" type="button" class="inline-flex items-center w-full justify-center rounded-full border border-transparent bg-pink-600 px-8 py-2 text-base font-medium text-white shadow-sm hover:bg-pink-700 focus:outline-none sm:ml-3 sm:w-auto sm:text-sm">
89+
<button @click="openModal = false" type="button" class="inline-flex items-center w-full justify-center rounded-full border border-transparent bg-pink-600 px-8 py-2 text-base font-medium text-white shadow-sm hover:bg-pink-700 focus:outline-none focus:ring focus:ring-pink-800 sm:ml-3 sm:w-auto sm:text-sm">
9090
Close
9191
</button>
9292
</div>

assets/components/results/CliOutputMismatch.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ export default {
6565

6666
<template #footer>
6767
<div class="flex justify-end">
68-
<button @click="openModal = false" type="button" class="inline-flex items-center w-full justify-center rounded-full border border-transparent bg-pink-600 px-8 py-2 text-base font-medium text-white shadow-sm hover:bg-pink-700 focus:outline-none sm:ml-3 sm:w-auto sm:text-sm">
68+
<button @click="openModal = false" type="button" class="inline-flex items-center w-full justify-center rounded-full border border-transparent bg-pink-600 px-8 py-2 text-base font-medium text-white shadow-sm hover:bg-pink-700 focus:outline-none focus:ring focus:ring-pink-800 sm:ml-3 sm:w-auto sm:text-sm">
6969
Close
7070
</button>
7171
</div>

assets/components/results/CliRun.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ export default {
8686

8787
<template #footer>
8888
<div class="flex justify-end">
89-
<button @click="openModal = false" type="button" class="inline-flex items-center w-full justify-center rounded-full border border-transparent bg-pink-600 px-8 py-2 text-base font-medium text-white shadow-sm hover:bg-pink-700 focus:outline-none sm:ml-3 sm:w-auto sm:text-sm">
89+
<button @click="openModal = false" type="button" class="inline-flex items-center w-full justify-center rounded-full border border-transparent bg-pink-600 px-8 py-2 text-base font-medium text-white shadow-sm hover:bg-pink-700 focus:outline-none focus:ring focus:ring-pink-800 sm:ml-3 sm:w-auto sm:text-sm">
9090
Close
9191
</button>
9292
</div>

assets/components/results/ComparisonFailure.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ export default {
6565

6666
<template #footer>
6767
<div class="flex justify-end">
68-
<button @click="openModal = false" type="button" class="inline-flex items-center w-full justify-center rounded-full border border-transparent bg-pink-600 px-8 py-2 text-base font-medium text-white shadow-sm hover:bg-pink-700 focus:outline-none sm:ml-3 sm:w-auto sm:text-sm">
68+
<button @click="openModal = false" type="button" class="inline-flex items-center w-full justify-center rounded-full border border-transparent bg-pink-600 px-8 py-2 text-base font-medium text-white shadow-sm hover:bg-pink-700 focus:outline-none focus:ring focus:ring-pink-800 sm:ml-3 sm:w-auto sm:text-sm">
6969
Close
7070
</button>
7171
</div>

0 commit comments

Comments
 (0)