See "img upload" in sidebar snet/molecules for variants, or go to info panel and look for it in "references" (@snet-img-upload)
Refer to modal frame found in view tab for complete structure without content.
A modal's default size is 640px
Modifier classes for modals
A modal with a content loading component behaves as follows:
<div class="sds-modal modal fade" id="speedinvestStockModalEmpty" aria-labelledby="speedinvestStockModalEmptyTitle" aria-modal="true" role="dialog" tabindex="-1">
<div class="sds-modal__dialog modal-dialog modal-dialog-centered modal-dialog-scrollable">
<div class="sds-modal__content modal-content">
<div class="sds-modal__header modal-header">
<button type="button" class="sds-btn -iconBtn -btnSecondary -ghost sds-modal__close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true" class="sds-icon sds-icon-close"></span>
</button>
</div>
<div class="sds-modal__body modal-body">
<div class="sds-stackXl">
<div class="sds-box -boxInsetLg -boxSunken">
<div class="sds-emptyState text-center">
<div class="sds-stackXs">
<span class="sds-emptyState__icon sds-icon sds-icon-graphline-l"></span>
<div class="sds-stackXxs">
<p class="sds-textBodyLg sds-textSemiBold">Tableau récapitulatif non disponible</p>
<p class="sds-textSemiBold">Le tableau indiquant la performance TWR & MWR de vos titres sera disponible lorsque vous aurez acquis des titres.</p>
</div>
</div>
</div>
</div>
<div class="sds-stackXs">
<div class="h3">Calculs Gains journaliers</div>
<p>La classification des produits financiers est celle opérée par la norme comptable IFRS 7.
Elle distingue trois types d’instruments financiers en fonction de la disponibilité et de la liquidité
des éléments de valorisation. Le premier niveau se caractérise par un prix disponible directement (prix
de marché) et une liquidité suffisante. La deuxième regroupe les produits qui peuvent être valorisés par
un modèle standard et des données cotées.</p>
</div>
<div class="sds-stackXs">
<div class="h3">Calculs Gains totaux</div>
<p>La classification des produits financiers est celle opérée par la norme comptable IFRS 7.
Elle distingue trois types d’instruments financiers en fonction de la disponibilité et de la liquidité
des
éléments de valorisation. Le premier niveau se caractérise par un prix disponible directement (prix de
marché) et une liquidité suffisante. La deuxième regroupe les produits qui peuvent être valorisés par un
modèle standard et des données cotées.</p>
</div>
</div>
</div>
</div>
</div>
</div>
{% extends "@snet-modal-base-frame" %}
{% block header %}
{% endblock %}
{% block body %}
<div class="{{ namespace }}stackXl">
<div class="{{ namespace }}box -boxInsetLg -boxSunken">
{% render "@empty-state--title-text",{
button: false,
icon: "icon-graphline-l",
title: "Tableau récapitulatif non disponible",
text: "Le tableau indiquant la performance TWR & MWR de vos titres sera disponible lorsque vous aurez acquis des titres."
},true %}
</div>
<div class="{{ namespace }}stackXs">
<div class="h3">Calculs Gains journaliers</div>
<p>La classification des produits financiers est celle opérée par la norme comptable IFRS 7.
Elle distingue trois types d’instruments financiers en fonction de la disponibilité et de la liquidité
des éléments de valorisation. Le premier niveau se caractérise par un prix disponible directement (prix
de marché) et une liquidité suffisante. La deuxième regroupe les produits qui peuvent être valorisés par
un modèle standard et des données cotées.</p>
</div>
<div class="{{ namespace }}stackXs">
<div class="h3">Calculs Gains totaux</div>
<p>La classification des produits financiers est celle opérée par la norme comptable IFRS 7.
Elle distingue trois types d’instruments financiers en fonction de la disponibilité et de la liquidité
des
éléments de valorisation. Le premier niveau se caractérise par un prix disponible directement (prix de
marché) et une liquidité suffisante. La deuxième regroupe les produits qui peuvent être valorisés par un
modèle standard et des données cotées.</p>
</div>
</div>
{% endblock %}
export default class ModalTrap {
constructor() {
// select all modals with the dialog role and modal classe.
// this ensures that only modals with the right attributes are selected
this.modals = document.querySelectorAll("[role='dialog'].modal");
// select all modal trigger buttons that don't have a data-dismiss attribute
// buttons with data-dismiss and data-toggle are typically only used within modals to trigger another modal
// we want to exclude these from the selection
this.modalToggles = document.querySelectorAll("[data-toggle='modal']:not([data-dismiss='modal'])");
// list of focusable elements inside a modal
this.focusableElements = [
"a[href]",
"button:not([disabled])",
"textarea:not([disabled])",
"input:not([disabled])",
"select:not([disabled])",
"[tabindex]:not([tabindex='-1'])"
];
// this will ensure that any modal with the dialog-scrollable modifier gets the intended behaviour
// all other modals will get ignored
this.modalScrollBody = ".modal-dialog-scrollable .modal-body"
// modal trigger element with same data-target value as id of triggered modal
this.modalTriggerElement = null;
// Used to store keydown handlers per modal
this.focusHandlers = new WeakMap();
this.init();
}
init() {
// loop over all modal trigger buttons
this.modalToggles.forEach(btn => {
// if data target value is erroneous or doesn't match any modal ID, return false
const targetSelector = btn.getAttribute('data-target');
if ((targetSelector === "#") || (!targetSelector)) return;
// on modal trigger interaction, set the modal trigger element's value to that of its DOM representation
// and store it
btn.addEventListener('click', (e) => {
e.preventDefault();
this.modalTriggerElement = e.target;
});
});
// loop over all modals
this.modals.forEach(modal => {
const closeBtn = modal.querySelector('.sds-modal__close');
// when a modal shown event is fired:
$(modal).on('shown.bs.modal', (e) => {
// check if a close button exists and focus it if so
if (closeBtn) {
closeBtn.focus();
}
else {
// if no close is present focus first focusable element instead
let focusableElements = this.getFocusableElements(modal);
let first = focusableElements[0];
first.focus()
}
// check for a scrollable body element inside the modal
// set focus trap inside modal while open
this.checkScrollabilityAndSetFocusTrap(modal);
/*
* if the modal gets shown through the bootstrap method instead of
* through a trigger element interaction, store the value of the trigger element
* so it can be focused when closing the modal
*
* this will ensure that no matter how the modal is shown,
* there's always an element to put the focus back on when closed
*
* closing a modal after navigating from modal to modal will also put the focus back on
* the initial modal trigger element or corresponding modal's trigger element, if applicable
* for this purpose, modal trigger elements inside modals that have the data-dismiss="modal" attribute are ignored
*/
let lastFocusedElement = document.querySelector("[data-toggle='modal']:not([data-dismiss='modal'])[data-target='#"+ e.target.getAttribute("id") +"']");
if (lastFocusedElement) {
this.modalTriggerElement = lastFocusedElement;
}
});
$(modal).on('hidden.bs.modal', (e) => {
// if it exists focus the modal's trigger element upon closing the modal
if (this.modalTriggerElement) {
this.modalTriggerElement.focus();
}
});
});
}
checkScrollabilityAndSetFocusTrap(modal) {
const modalBodySelector = this.modalScrollBody;
// check if the modal scrollable modifier is applied to the current modal
// if not, the last element to receive focus will be the modal itself
const modalBody = modal.querySelector(modalBodySelector) ? modal.querySelector(modalBodySelector) : modal;
// add or remove tabindex depending on whether the modal body area is scrollable
function setScrollability () {
const isScrollable = modalBody.scrollHeight > modalBody.clientHeight;
if (isScrollable) {
// Ensure it's focusable
if (!modalBody.hasAttribute('tabindex')) {
modalBody.setAttribute('tabindex', '0');
}
} else {
if (modalBody.hasAttribute('tabindex')) {
modalBody.removeAttribute('tabindex');
}
}
}
// check if modal body is scriollable or not on page load
setScrollability();
// Initial trap
this.trapFocus(modal);
// Observe scrollability changes
const observer = new ResizeObserver(() => {
// check if modal body becomes scrollable on resize
setScrollability();
this.trapFocus(modal);
});
// Required to detect resizing/scrollability
observer.observe(modalBody);
}
// trap the kesyboard focus navigfation inside the modal while it is shown
trapFocus(modal) {
const focusable = this.getFocusableElements(modal);
// abort if no focusable elements are found inside modal
if (!focusable.length) return;
// define first and last focusable elements inside modal
const first = focusable[0];
const last = focusable[focusable.length - 1];
// Remove old handler if exists
if (this.focusHandlers.has(modal)) {
modal.removeEventListener('keydown', this.focusHandlers.get(modal));
}
// move focus from first to last and vice-versa when using tab or shift+tab
const handleKeydown = (e) => {
if (e.key !== 'Tab') return;
if (e.shiftKey && document.activeElement === first) {
e.preventDefault();
last.focus();
} else if (!e.shiftKey && document.activeElement === last) {
e.preventDefault();
first.focus();
}
};
modal.addEventListener('keydown', handleKeydown);
this.focusHandlers.set(modal, handleKeydown);
}
// function to get list of focusable elements in modal
getFocusableElements(container) {
// stringify list of focusable element to pass it to querySelectorAll
const selector = this.focusableElements.join();
return container.querySelectorAll(selector);
}
}