Skip to content

Commit

Permalink
Various fixes for Modals (#326)
Browse files Browse the repository at this point in the history
* Resync with master (#9)

* Create form-input-static.vue

New form-static input

* Added form-input-static compoinent

* Refactored static input

Refactored to use the new `<b-form-input-static>` component

* Switch to bFormInputStatic

Updated child component var to bFormInputStatic to follow proper naming conventions

* Removed lazyFormatter from static-input

* Added trailing semi-colon

To make CircleCI happy

* Added missing 'this'

* [nav-item] add dropdown class

* Added <slot> for robustness

* new b-form-input-static component (#292)

* Create form-input-static.vue

New form-static input

* Added form-input-static compoinent

* Refactored static input

Refactored to use the new `<b-form-input-static>` component

* Switch to bFormInputStatic

Updated child component var to bFormInputStatic to follow proper naming conventions

* Removed lazyFormatter from static-input

* Added trailing semi-colon

To make CircleCI happy

* Added missing 'this'

* Added <slot> for robustness

* fixed missing `.vue` extension on import

* Added missing extension on component import (#293)

* Optimized import order in form-input.vue (#294)

* Added missing extension on component import

* Optimized import order

* Refactoring focus timing

* Update to include modal

* Ensure focusable item if focused when modal opens

Ensures that the first focusable item in the modal is focused when it is shown.
When selecting the item to focus:
- First looks in footer
- Then body
- Then header
- Else focus the modal itself

Also included generate-id mixin for ARIA labeling

* Added missing space

* Fixed Typo and referenced _id on $emit/$on

* fixed focusin event

Changed from listing for focus to focusin for enforceFocus hanlder

* Moved add/removeEventlistener to show()/hide()

enforceFocus event listeners are now only instantiated when the modal is shown.

* Minor updates to focus handler

* Feature to return focus to trigger element

Added a second optional argument to the `show::modal` event to allow specifying the element (i.e. button, link) to return focus to when Modal closes.

* Allowed passing original trigger element

Allow to return focus to triggering element when modal is closed.

* Allow for prop to specify return focus element

added in prop `returnFocus` to specify element to re-focus to when modal closes.

Accepts either a CSS selector string (i.e. '#mybutton') or an `HTMLElement` reference. If CSS selector string matches more than one element, then only the first element is re-focused.

* Removed auto_id generation

`aria-labelledby` and `aria-describedby` will not be present (by setting bound attribute to null) if an `id` is not specified for the modal.

* Removed reference to modal.vue
  • Loading branch information
tmorehouse authored and pi0 committed May 2, 2017
1 parent ae3638e commit 0c5b9c6
Show file tree
Hide file tree
Showing 2 changed files with 73 additions and 17 deletions.
88 changes: 72 additions & 16 deletions lib/components/modal.vue
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,14 @@
tabindex="-1"
role="document"
ref="content"
:aria-labeledby="hideHeader ? '' : (id + '_modal_title')"
:aria-describedby="id + '_modal_body'"
:aria-labelledby="(hideHeader || !id) ? null : (id + '_modal_title')"
:aria-describedby="id ? (id + '_modal_body') : null"
@click.stop
>

<header class="modal-header" v-if="!hideHeader">
<header class="modal-header" ref="header" v-if="!hideHeader">
<slot name="modal-header">
<h5 class="modal-title" :id="id + '_modal_title'">
<h5 class="modal-title" :id="id ? (id + '_modal_title') : null">
<slot name="modal-title">{{title}}</slot>
</h5>
<button type="button"
Expand All @@ -41,11 +41,11 @@
</slot>
</header>

<div class="modal-body" :id="id + '_modal_body'">
<div class="modal-body" ref="body" :id="id ? (id + '_modal_body') : null">
<slot></slot>
</div>

<footer class="modal-footer" v-if="!hideFooter">
<footer class="modal-footer" ref="footer" v-if="!hideFooter">
<slot name="modal-footer">
<b-btn variant="secondary" @click="hide(false)">{{closeTitle}}</b-btn>
<b-btn variant="primary" @click="hide(true)">{{okTitle}}</b-btn>
Expand All @@ -69,7 +69,7 @@
opacity: 0 !important;
}
/* Make modal display as block instead of inline style, and because Vue's v-show deletes inline "display" style*/
/* Make modal display as block instead of inline style, and because Vue's v-show deletes inline "display" style */
.modal {
display: block;
}
Expand All @@ -78,11 +78,21 @@
<script>
import bBtn from './button.vue';
const FOCUS_SELECTOR = [
'button:not([disabled])',
'input:not([disabled])',
'select:not([disabled])',
'textarea:not([disabled])',
'a:not([disabled]):not(.disabled)',
'[tabindex]:not([disabled]):not(.disabled)'
].join(',');
export default {
components: {bBtn},
data() {
return {
is_visible: false
is_visible: false,
return_focus: this.returnFocus || null
};
},
model: {
Expand Down Expand Up @@ -153,6 +163,10 @@
hideHeaderClose: {
type: Boolean,
default: false
},
returnFocus: {
type: [String, HTMLElement],
default: null
}
},
methods: {
Expand All @@ -165,6 +179,16 @@
this.body.classList.add('modal-open');
this.$emit('shown');
this.$emit('change', true);
if (typeof document !== 'undefined') {
// Guard against infinite focus loop
document.removeEventListener('focusin', this.enforceFocus, false);
// Handle constrained focus
document.addEventListener('focusin', this.enforceFocus, false);
}
this.$nextTick(function () {
// Make sure DOM is updated before focusing
this.focusFirst();
});
},
hide(isOK) {
if (!this.is_visible) {
Expand Down Expand Up @@ -192,6 +216,12 @@
// Hide if not canceled
if (!canceled) {
if (typeof document !== 'undefined') {
// Remove focus handler
document.removeEventListener('focusin', this.enforceFocus, false);
// Return focus to original button/trigger element if provided
this.returnFocusTo();
}
this.is_visible = false;
this.$root.$emit('hidden::modal', this.id);
this.body.classList.remove('modal-open');
Expand All @@ -204,14 +234,42 @@
}
},
onEsc() {
// If ESC presses, hide modal
// If ESC pressed, hide modal
if (this.is_visible && this.closeOnEsc) {
this.hide();
}
},
focusFirst() {
// Focus the modal's first focusable item, searching footer, then body, then header, else the modal
let el;
if (this.$refs.footer) {
el = this.$refs.footer.querySelector(FOCUS_SELECTOR);
}
if (!el && this.$refs.body) {
el = this.$refs.body.querySelector(FOCUS_SELECTOR);
}
if (!el && this.$refs.header) {
el = this.$refs.header.querySelector(FOCUS_SELECTOR);
}
if (!el) {
el = this.$refs.content;
}
el.focus();
},
returnFocusTo() {
if (this.return_focus) {
const el = (typeof this.return_focus === 'string') ?
document.querySelector(this.returnFocus) :
this.return_focus;
if (el && typeof el.focus === 'function') {
el.focus();
}
}
},
enforceFocus(e) {
// If focus leaves modal, bring it back
// eventListener bound on document
// Event Listener bound on document
if (this.is_visible &&
document !== e.target &&
this.$refs.content &&
Expand All @@ -222,8 +280,9 @@
}
},
created() {
this.$root.$on('show::modal', id => {
this.$root.$on('show::modal', (id, triggerEl) => {
if (id === this.id) {
this.return_focus = triggerEl || this.return_focus || this.returnFocus || null;
this.show();
}
});
Expand All @@ -235,17 +294,14 @@
});
},
mounted() {
if (typeof document !== 'undefined') {
document.addEventListener('focus', this.enforceFocus);
}
if (this.visible === true) {
this.show();
}
},
destroyed() {
// Make sure event listener is rmoved
if (typeof document !== 'undefined') {
document.removeEventListener('focus', this.enforceFocus);
document.removeEventListener('focusin', this.enforceFocus, false);
}
}
};
Expand Down
2 changes: 1 addition & 1 deletion lib/directives/modal.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ export default {
bind(el, binding) {
target(el, binding, listen_types, ({targets, vm}) => {
targets.forEach(target => {
vm.$root.$emit('show::modal', target);
vm.$root.$emit('show::modal', target, el);
});
});
}
Expand Down

0 comments on commit 0c5b9c6

Please sign in to comment.