@pocketprep/ui-kit 3.1.0 → 3.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/@pocketprep/ui-kit.js +1895 -1883
- package/dist/@pocketprep/ui-kit.js.map +1 -1
- package/dist/@pocketprep/ui-kit.umd.cjs +8 -8
- package/dist/@pocketprep/ui-kit.umd.cjs.map +1 -1
- package/lib/components/Modal/ModalContainer.vue +24 -18
- package/lib/components/SidePanels/SidePanel.vue +48 -37
- package/package.json +1 -1
|
@@ -52,24 +52,30 @@ export default class ModalContainer extends Vue {
|
|
|
52
52
|
|
|
53
53
|
focusListener: Parameters<typeof addEventListener>[1] | null = null
|
|
54
54
|
savedYPosition = 0
|
|
55
|
+
modalNumber = 0
|
|
56
|
+
|
|
57
|
+
numberOfModals () {
|
|
58
|
+
return document.querySelectorAll('.uikit-modal-container').length
|
|
59
|
+
}
|
|
55
60
|
|
|
56
61
|
mounted () {
|
|
62
|
+
this.modalNumber = this.numberOfModals()
|
|
57
63
|
// Prevent an error where multiple modals trigger a loop
|
|
58
64
|
// TODO: Find a way to have only the latest modal's focusListener active
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
const focusableSelectors = 'button, [href], input, select, textarea, [tabindex]'
|
|
65
|
+
|
|
66
|
+
const focusableSelectors = 'button, [href], input, select, textarea, [tabindex]'
|
|
62
67
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
68
|
+
// Reset focus to last element in modal so next tab will move it to top
|
|
69
|
+
const modalContainerEl = this.$refs['modalContainer'] as HTMLElement
|
|
70
|
+
const modalFocusableEls = Array.from<HTMLElement>(modalContainerEl.querySelectorAll(focusableSelectors))
|
|
71
|
+
if (modalFocusableEls.length) {
|
|
72
|
+
modalFocusableEls[modalFocusableEls.length - 1]?.focus()
|
|
73
|
+
modalFocusableEls[modalFocusableEls.length - 1]?.blur()
|
|
74
|
+
}
|
|
70
75
|
|
|
71
|
-
|
|
72
|
-
|
|
76
|
+
// Trap the user's focus within the modal - don't allow focusing elements behind the overlay
|
|
77
|
+
this.focusListener = event => {
|
|
78
|
+
if (this.modalNumber === this.numberOfModals()) { // Only focus on the last open panel
|
|
73
79
|
const target = (event as FocusEvent).target as HTMLElement // The element receiving focus
|
|
74
80
|
const isFocusOutside = target && modalContainerEl && !modalContainerEl.contains(target)
|
|
75
81
|
const hasCalendarClass = target
|
|
@@ -90,20 +96,20 @@ export default class ModalContainer extends Vue {
|
|
|
90
96
|
if (firstFocusableModalChild) {
|
|
91
97
|
const relatedTarget = (event as FocusEvent).relatedTarget // The element last focused
|
|
92
98
|
if (relatedTarget === firstFocusableModalChild && lastFocusableModalChild) {
|
|
93
|
-
|
|
99
|
+
// If focus moves from first element -> outside modal, focus the last element instead
|
|
94
100
|
lastFocusableModalChild.focus()
|
|
95
101
|
} else if (relatedTarget === lastFocusableModalChild && firstFocusableModalChild) {
|
|
96
|
-
|
|
102
|
+
// If focus moves from last element -> outside modal, focus the first element instead
|
|
97
103
|
firstFocusableModalChild.focus()
|
|
98
104
|
} else if (relatedTarget && relatedTarget instanceof HTMLElement) {
|
|
99
|
-
|
|
105
|
+
// If focus goes outside in a different way, return focus to where it came from if possible
|
|
100
106
|
relatedTarget.focus()
|
|
101
107
|
} else {
|
|
102
|
-
|
|
108
|
+
// Otherwise, just return focus to the first element
|
|
103
109
|
firstFocusableModalChild.focus()
|
|
104
110
|
}
|
|
105
111
|
} else {
|
|
106
|
-
|
|
112
|
+
// If the modal doesn't have any focusable children, focus the container instead
|
|
107
113
|
if (modalContainerEl.tabIndex === -1) {
|
|
108
114
|
modalContainerEl.tabIndex = 0
|
|
109
115
|
}
|
|
@@ -111,8 +117,8 @@ export default class ModalContainer extends Vue {
|
|
|
111
117
|
}
|
|
112
118
|
}
|
|
113
119
|
}
|
|
114
|
-
document.addEventListener('focusin', this.focusListener)
|
|
115
120
|
}
|
|
121
|
+
document.addEventListener('focusin', this.focusListener)
|
|
116
122
|
|
|
117
123
|
// prevent scrolling outside of modal
|
|
118
124
|
const openModalCount = Number(document.body.getAttribute('data-openModalCount'))
|
|
@@ -142,6 +142,7 @@ export default class SidePanel extends Vue {
|
|
|
142
142
|
notContentHeight = this.tabs && this.tabs.length ? 262 : 218
|
|
143
143
|
focusListener: Parameters<typeof addEventListener>[1] | null = null
|
|
144
144
|
savedYPosition = 0
|
|
145
|
+
sidePanelNumber = 0
|
|
145
146
|
|
|
146
147
|
get sidePanelWidth () {
|
|
147
148
|
return this.width === 'large'
|
|
@@ -151,57 +152,67 @@ export default class SidePanel extends Vue {
|
|
|
151
152
|
: this.width
|
|
152
153
|
}
|
|
153
154
|
|
|
155
|
+
numberOfSidepanels () {
|
|
156
|
+
return document.querySelectorAll('.uikit-side-panel').length
|
|
157
|
+
}
|
|
158
|
+
|
|
154
159
|
mounted () {
|
|
155
|
-
|
|
156
|
-
const sidePanelEl = this.$refs['uikit-side-panel'] as HTMLElement | undefined
|
|
157
|
-
sidePanelEl?.addEventListener('transitionend', () => {
|
|
158
|
-
const titleEl = this.$refs['uikit-side-panel__title'] as HTMLElement | undefined
|
|
159
|
-
titleEl?.focus()
|
|
160
|
-
})
|
|
160
|
+
this.sidePanelNumber = this.numberOfSidepanels()
|
|
161
161
|
|
|
162
162
|
// delay opening to show animation
|
|
163
163
|
setTimeout(() => {
|
|
164
164
|
this.openSidePanel = true
|
|
165
165
|
}, 1)
|
|
166
166
|
|
|
167
|
+
|
|
168
|
+
// Focus title after opening transition
|
|
169
|
+
const sidePanelEl = this.$refs['uikit-side-panel'] as HTMLElement | undefined
|
|
170
|
+
sidePanelEl?.addEventListener('transitionend', () => {
|
|
171
|
+
const titleEl = this.$refs['uikit-side-panel__title'] as HTMLElement | undefined
|
|
172
|
+
titleEl?.focus()
|
|
173
|
+
})
|
|
174
|
+
|
|
167
175
|
// Trap the user's focus within the side panel - don't allow focusing elements behind the overlay
|
|
168
176
|
this.focusListener = event => {
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
177
|
+
if (this.sidePanelNumber === this.numberOfSidepanels()) { // Only focus on the last open panel
|
|
178
|
+
const target = (event as FocusEvent).target as HTMLElement // The element receiving focus
|
|
179
|
+
const sidePanelContainerEl = this.$refs['sidepanelContainer'] as HTMLElement
|
|
180
|
+
const isFocusOutside = target && sidePanelContainerEl && !sidePanelContainerEl.contains(target)
|
|
181
|
+
const hasCalendarClass = target
|
|
182
|
+
&& Array.from(target.classList).find(
|
|
183
|
+
c => c === 'button-next-month' || c === 'button-previous-month' || c === 'day-item'
|
|
184
|
+
)
|
|
185
|
+
if (isFocusOutside && !hasCalendarClass) {
|
|
186
|
+
const focusableSidePanelChildren = Array.from<HTMLElement>(sidePanelContainerEl.querySelectorAll(
|
|
187
|
+
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
|
|
188
|
+
))
|
|
189
|
+
const firstFocusableSidePanelChild = focusableSidePanelChildren.find(
|
|
190
|
+
el => !!el.getBoundingClientRect().width
|
|
191
|
+
)
|
|
192
|
+
const reversedSidePanelChildren = [ ...focusableSidePanelChildren ].reverse()
|
|
193
|
+
const lastFocusableSidePanelChild = reversedSidePanelChildren.find(
|
|
194
|
+
el => !!el.getBoundingClientRect().width
|
|
195
|
+
)
|
|
196
|
+
if (firstFocusableSidePanelChild) {
|
|
197
|
+
const relatedTarget = (event as FocusEvent).relatedTarget // The element last focused
|
|
198
|
+
if (relatedTarget === firstFocusableSidePanelChild && lastFocusableSidePanelChild) {
|
|
199
|
+
// If focus moves from first element -> outside side panel, focus the last element instead
|
|
200
|
+
lastFocusableSidePanelChild.focus()
|
|
201
|
+
} else {
|
|
202
|
+
// If focus goes outside the side panel in any other way, focus the first element
|
|
203
|
+
firstFocusableSidePanelChild.focus()
|
|
204
|
+
}
|
|
192
205
|
} else {
|
|
193
|
-
// If
|
|
194
|
-
|
|
206
|
+
// If the side panel doesn't have any focusable children, focus the container instead
|
|
207
|
+
if (sidePanelContainerEl.tabIndex === -1) {
|
|
208
|
+
sidePanelContainerEl.tabIndex = 0
|
|
209
|
+
}
|
|
210
|
+
sidePanelContainerEl.focus()
|
|
195
211
|
}
|
|
196
|
-
} else {
|
|
197
|
-
// If the side panel doesn't have any focusable children, focus the container instead
|
|
198
|
-
if (sidePanelContainerEl.tabIndex === -1) {
|
|
199
|
-
sidePanelContainerEl.tabIndex = 0
|
|
200
|
-
}
|
|
201
|
-
sidePanelContainerEl.focus()
|
|
202
212
|
}
|
|
203
213
|
}
|
|
204
214
|
}
|
|
215
|
+
|
|
205
216
|
document.addEventListener('focusin', this.focusListener)
|
|
206
217
|
|
|
207
218
|
// prevent scrolling outside of modal
|