@pidila/scampi
Advanced tools
Comparing version 0.6.1 to 0.7.0
Changelog | ||
==================================================================== | ||
v0.6 | ||
v0.7 | ||
-------------------------------------------------------------------- | ||
Cette version constitue une étape intermédiaire vers une version 1.0.0 | ||
***changements majeurs*** : | ||
- plus aucun script ne nécessite jquery (concerne : anchor-focus, collapse, menu-simple, modal, skip-link, textarea-counter, u-comments, u-palette) ; | ||
- les scripts des modules modal et collapse ont été réécrits | ||
- nouveau module select-a11y, qui transforme un select (simple ou multiple) en liste de suggestions avec champ de recherche | ||
### Modules | ||
modules/buttons/buttons-color : | ||
- correction des variations de couleurs qui présentaient des déclinaisons avec des contrastes insuffisants | ||
modules/collapse : | ||
- modifications pour permettre d'ouvrir tout par zones (ou tous les collapses de toute la page) | ||
modules/lien-composite : | ||
- nouveau module permettant de créer un lien composite accessible (par exemple composé d'une image, un titre, un chapeau) | ||
- modules/forms/forms-inline : | ||
amélioration de la présentation par défaut pour que les éléments ne soient pas tous collés les uns aux autres | ||
v0.6.1 | ||
-------------------------------------------------------------------- | ||
Scampi est désormais également disponible en tant que module npm. | ||
@@ -13,2 +37,4 @@ | ||
v0.5 | ||
@@ -15,0 +41,0 @@ -------------------------------------------------------------------- |
@@ -14,4 +14,5 @@ # Adobe-blank | ||
Pour mettre en œuvre ce module vous devez passer le setting `$enable-adobe-blank` à `true`et affecter la class `blank` à l'élément que vous voulez masquer. | ||
### Configuration | ||
Pour mettre en œuvre ce module vous devez passer le setting `$enable-adobe-blank` à `true`et affecter la class `blank` à l'élément que vous voulez masquer. Par défaut, la valeur de `$enable-adobe-blank` est à `false`. | ||
@@ -18,0 +19,0 @@ Exemples d’utilisation |
@@ -11,2 +11,7 @@ # Alert | ||
Note : ce module est issu du framework Bootstrap. | ||
Utilisation | ||
------------- | ||
Si vous utilisez ce composant pour afficher des messages de façon dynamique (apparition suite à l'activation d'un bouton ou la validation d'un formulaire sans rechargement de page par exemple) et que vous souhaitez faire vocaliser le contenu de ces messages par les lecteurs d'écran sans devoir déplacer le focus sur le message il faudra : | ||
@@ -18,4 +23,38 @@ | ||
Note : ce module est issu du framework Bootstrap. | ||
### Configuration | ||
Les variables proposées dans ce module sont : | ||
#### Alerte | ||
- `$alert-padding` : padding du bloc d'alerte, sa valeur par défaut est `($spacer / 2) $spacer`; | ||
- `$alert-border-radius` : border-radius du bloc d'alerte, sa valeur par défaut est `$border-radius`; | ||
- `$alert-link-font-weight` : font-weight du bloc d'alerte, sa valeur par défaut est `bold`; | ||
- `$alert-border-width` : border-width du bloc d'alerte, sa valeur par défaut est `$border-width`; | ||
#### Succès | ||
- `$alert-success-bg` : couleur de fond, sa valeur par défaut est `$state-success-bg`; | ||
- `$alert-success-text` : couleur du texte, sa valeur par défaut est `$state-success-text`; | ||
- `$alert-success-border` : couleur de la bordure, sa valeur par défaut est `$state-success-border`; | ||
#### Info | ||
- `$alert-info-bg` : couleur de fond, sa valeur par défaut est `$state-info-bg`; | ||
- `$alert-info-text` : couleur du texte, sa valeur par défaut est `$state-info-text`; | ||
- `$alert-info-border` : couleur de la bordure, sa valeur par défaut est `$state-info-border`; | ||
#### Warning | ||
- `$alert-warning-bg` : couleur de fond, sa valeur par défaut est `$state-warning-bg`; | ||
- `$alert-warning-text` : couleur du texte, sa valeur par défaut est `$state-warning-text`; | ||
- `$alert-warning-border` : couleur de la bordure, sa valeur par défaut est `$state-warning-border`; | ||
#### Danger | ||
- `$alert-danger-bg` : couleur de fond, sa valeur par défaut est `$state-danger-bg`; | ||
- `$alert-danger-text` : couleur du texte, sa valeur par défaut est `$state-danger-text`; | ||
- `$alert-danger-border` : couleur de la bordure, sa valeur par défaut est `$state-danger-border`; | ||
#### Emergency | ||
- `$alert-emergency-bg` : couleur de fond, sa valeur par défaut est `$state-emergency-bg`; | ||
- `$alert-emergency-text` : couleur du texte, sa valeur par défaut est `$state-emergency-text`; | ||
- `$alert-emergency-border` : couleur de la bordure, sa valeur par défaut est `$state-emergency-border`; | ||
Exemples d'utilisation | ||
@@ -22,0 +61,0 @@ ------------- |
@@ -8,14 +8,48 @@ /*! | ||
$(document).ready(function(){ | ||
var Scampi = Scampi || {}; | ||
$("a[href^='#']").click(function(evt){ | ||
var anchortarget = $(this).attr("href"); | ||
$(anchortarget).attr("tabindex", -1).focus(); | ||
}); | ||
Scampi.anchorFocus = function anchorFocus(){ | ||
var matches = Element.prototype.matches || Element.prototype.msMatchesSelector || Element.prototype.webkitMatchesSelector; | ||
var closest = Element.prototype.closest; | ||
// Fixes anchor focus in Chrome/Safari/IE by setting the tabindex of the | ||
// target container to -1 on page load | ||
if (window.location.hash) { | ||
$(window.location.hash).attr("tabindex", -1).focus(); | ||
if (!closest) { | ||
closest = function(s) { | ||
var el = this; | ||
do { | ||
if (matches.call(el, s)) return el; | ||
el = el.parentElement || el.parentNode; | ||
} while (el !== null && el.nodeType === 1); | ||
return null; | ||
}; | ||
} | ||
}); | ||
function focusTarget(hash){ | ||
var splitedHash = hash.split('#'); | ||
var target = document.getElementById(splitedHash[1] || splitedHash[0]); | ||
if(!target){ | ||
return; | ||
} | ||
target.setAttribute('tabindex', -1); | ||
target.focus(); | ||
} | ||
if(window.location.hash){ | ||
focusTarget(window.location.hash); | ||
} | ||
document.body.addEventListener("click", function(evt){ | ||
var link = closest.call(evt.target, ("a[href^='#']")); | ||
if(!link){ | ||
return; | ||
} | ||
focusTarget(link.href); | ||
}, true) | ||
} | ||
Scampi.anchorFocus(); |
@@ -9,2 +9,7 @@ # Blockquote | ||
Note : ce module est basé sur celui du framework Bootstrap. | ||
Utilisation | ||
------------- | ||
L’ajout de la class `blockquote` (ou `blockquote-reverse` pour un alignement à droite) sur l'ensemble du blockquote et de la class `blockquote-footer` sur la mention de la source applique automatiquement les styles par défaut du mixin blockquote. | ||
@@ -20,6 +25,9 @@ | ||
Pour personnaliser la présentation, on peut modifier les paramètres du mixin. | ||
### Configuration | ||
Note : ce module est basé sur celui du framework Bootstrap. | ||
Les variables proposées dans ce module sont : | ||
- `$blockquote-small-color` : couleur du texte de la source, sa valeur par défaut est `$gray`; | ||
- `$blockquote-border-color` : Couleur de la bordure, sa valeur par défaut est `$gray-8`; | ||
Exemple d'utilisation | ||
@@ -26,0 +34,0 @@ ------------- |
@@ -10,2 +10,5 @@ # Breadcrumb | ||
Utilisation | ||
------------ | ||
Les styles sont portés par la class `breadcrumb`. | ||
@@ -19,4 +22,7 @@ | ||
Vous pouvez changer le caractère de séparation dans les styles du module avec la variable `$breadcrumb-symbol`. Sa valeur par defaut est ">". | ||
### Configuration | ||
La variable proposée dans ce module est : | ||
- `$breadcrumb-symbol` : caractère de séparation, sa valeur par défaut est `\203A` (soit le caractère ">"); | ||
Utilisation | ||
@@ -23,0 +29,0 @@ ----------- |
@@ -14,10 +14,33 @@ # Buttons | ||
Le partial `_styles-buttons.scss` est indispensable tandis que les autres partials scss du module préfixés `_styles-buttons-xxxxxxx` sont optionnels en fonction des besoins du projet. | ||
Utilisation | ||
----------- | ||
Le partial `_styles-buttons.scss` est indispensable tandis que les autres partials scss du module préfixés `_styles-buttons-xxxxxxx` sont optionnels en fonction des besoins du projet. | ||
La présentation des boutons passe presque uniquement par la personnalisation des variables et l'ajout de classes sur les éléments html. | ||
### Configuration | ||
Les variables proposées dans ce module sont : | ||
- `$input-btn-border-width` : bordure de l'input, sa valeur par défaut est `$border-width`; | ||
- `$btn-border-width` : bordure du bouton, sa valeur par défaut est `$input-btn-border-width`; | ||
- `$btn-padding-x` : padding horizontal, sa valeur par défaut est `1rem`; | ||
- `$btn-padding-y` : padding vertical, sa valeur par défaut est `.25rem`; | ||
- `$btn-default-color` : couleur du texte, sa valeur par défaut est `$gray-3`; | ||
- `$btn-default-bg` : couleur du fond, sa valeur par défaut est `$gray-9`; | ||
- `$btn-default-border` : couleur de la bordure, sa valeur par défaut est `darken($btn-default-bg, 10%)`; | ||
- `$btn-primary-color` : couleur du bouton principal, sa valeur par défaut est `#fff`; | ||
- `$btn-primary-bg` : couleur de fond du bouton principal, sa valeur par défaut est `$primary-color`; | ||
- `$btn-primary-border` : couleur de la bordure du bouton principal, sa valeur par défaut est `darken($btn-primary-bg, 10%)`; | ||
- `$btn-primary-outline` : couleur de l'outline du bouton principal, sa valeur par défaut est `$primary-color`; | ||
- `$btn-secondary-color` : couleur du bouton secondaire, sa valeur par défaut est `$gray-3`; | ||
- `$btn-secondary-bg` : couleur de fond du bouton secondaire, sa valeur par défaut est `#fff`; | ||
- `$btn-secondary-border` : couleur de la bordure du bouton secondaire, sa valeur par défaut est `$gray-7`; | ||
- `$btn-secondary-outline` : couleur de l'outline du bouton secondaire, sa valeur par défaut est `darken($btn-secondary-color, 30%)`; | ||
De nombreuses autres variables personnalisables sont décrites dans le fichier [modules/buttons/_index.scss](https://gitlab.com/pidila/scampi/blob/master/modules/buttons/_index.scss). | ||
Tout le socle commun des boutons est donné par la class `btn` ; d'autres classes viennent affiner la présentation ou ajouter des styles pour des besoins spécifiques. On trouve ainsi des partials pour : | ||
@@ -42,4 +65,4 @@ | ||
Exemples pour les éléments de base | ||
--------------------------------------------------------------------- | ||
Exemples d'utilisation | ||
---------------------- | ||
@@ -46,0 +69,0 @@ ````html |
@@ -11,3 +11,3 @@ # Collapse | ||
Note : ce module a été développé par [Nicolas Hoffmann](http://a11y.nicolas-hoffmann.net/hide-show/). | ||
Note : ce module utilise le script « _Accordéon_ » développé par [Switch](https://a11y.switch.paris/accordeon.html). | ||
@@ -20,12 +20,16 @@ | ||
Pour que le script fonctionne correctement, le code html doit répondre à deux impératifs : | ||
Pour que le script fonctionne correctement, le code html doit répondre à trois impératifs : | ||
1. L’élément déclencheur doit porter la class `js-expandmore` et l’élément à montrer/cacher doit porter la class `js-to_expand`. | ||
2. Ces deux éléments doivent être voisins immédiats dans la source. | ||
1. Les éléments à plier et déplier doivent se trouver dans un élément commun comportant la classe `js-expandmore`. | ||
2. L’élément déclencheur doit porter la class `js-expandmore-title` et l’élément à montrer/cacher doit porter la class `js-expandmore-panel`. | ||
3. Ces deux éléments doivent être voisins immédiats dans la source. | ||
Par défaut un seul élément est dépliable à la fois. Pour permettre l'ouverture de tous les éléments en même temps il faut alors rajouter l'attribut `data-multiselectable="true"` sur l'élément ayant la classe `js-expandmore`. | ||
Les styles présents dans le module sont les styles minimaux pour son bon fonctionnement. | ||
Il est possible d’ajouter un bouton pour déplier / replier tous les blocs de la page. Ce bouton doit porter la classe `js-expandmore-all`. | ||
Le texte affiché dans ce bouton se configure dans le fichier javascript. | ||
Il est possible d’ajouter un bouton pour déplier / replier tous les blocs de la page. Ce bouton doit porter la classe `js-expandmore-all`. | ||
Le texte affiché dans ce bouton se configure dans les attributs `data-expandtext` pour le texte à afficher pour tout déplier et `data-collapsetext` pour celui à afficher pour tout replier. | ||
De même, il es possible d'utiliser ce même bouton pour déplier / replier un seul bloc de la page en ajoutant l'attribute `data-controls` avec pour valeur l'`id` du block à controller. (Voir l'exemple ci-dessous.) | ||
@@ -54,28 +58,35 @@ ### Script associé | ||
Exemple | ||
Exemple d'utilisation | ||
------------------------------------------------------------------- | ||
```html | ||
<button class="js-expandmore-all btn">Tout déplier</button> | ||
<button class="js-expandmore-all btn" data-expand="true" data-expandtext="Tout déplier" data-collapsetext="Tout replier">Tout déplier</button> | ||
<h3 class="js-expandmore">Question 1</h3> | ||
<div class="js-to_expand"> | ||
<p>Le contenu caché peut être constitué de tout élément html : paragraphes, titres, tableaux, images…</p> | ||
</div> | ||
<h2> | ||
Questions | ||
<button class="js-expandmore-all btn" data-expand="true" data-expandtext="Déplier « Questions »" data-collapsetext="Replier « Questions »" data-controls="questions">Déplier « Questions »</button> | ||
</h2> | ||
<h3 class="js-expandmore">Question 2</h3> | ||
<div class="js-to_expand"> | ||
<p>Le contenu caché peut être constitué de tout élément html : paragraphes, titres, tableaux, images…</p> | ||
</div> | ||
<div id="questions" class="js-expandmore"> | ||
<h3 class="js-expandmore-title">Question 1</h3> | ||
<div class="js-expandmore-panel"> | ||
<p>Le contenu caché peut être constitué de tout élément html : paragraphes, titres, tableaux, images…</p> | ||
</div> | ||
<h3 class="js-expandmore">Faut-il une autre question sur plusieurs lignes pour vérifier le comportement du symbole d'ouverture et de fermeture ?</h3> | ||
<div class="js-to_expand is-opened"> | ||
<p>Oui, il le faut</p> | ||
<p>Ce collapse est ouvert par défaut et peut être replié.</p> | ||
</div> | ||
<h3 class="js-expandmore-title">Question 2</h3> | ||
<div class="js-expandmore-panel"> | ||
<p>Le contenu caché peut être constitué de tout élément html : paragraphes, titres, tableaux, images…</p> | ||
</div> | ||
<h3 class="js-expandmore">Question 4</h3> | ||
<div class="js-to_expand"> | ||
<p>Le contenu caché peut être constitué de tout élément html : paragraphes, titres, tableaux, images…</p> | ||
<h3 class="js-expandmore-title">Faut-il une autre question sur plusieurs lignes pour vérifier le comportement du symbole d'ouverture et de fermeture ?</h3> | ||
<div class="js-expandmore-panel is-opened"> | ||
<p>Oui, il le faut</p> | ||
<p>Ce collapse est ouvert par défaut et peut être replié.</p> | ||
</div> | ||
<h3 class="js-expandmore-title">Question 4</h3> | ||
<div class="js-expandmore-panel"> | ||
<p>Le contenu caché peut être constitué de tout élément html : paragraphes, titres, tableaux, images…</p> | ||
</div> | ||
</div> | ||
``` |
@@ -1,134 +0,588 @@ | ||
jQuery(document).ready(function ($) { | ||
/** | ||
* @accede-web/accordion - WAI-ARIA accordion plugin based on AcceDe Web accessibility guidelines | ||
* @version v1.0.1 | ||
* @link http://a11y.switch.paris/ | ||
* @license ISC | ||
**/ | ||
/* | ||
* jQuery simple and accessible hide-show system (collapsible regions), using ARIA | ||
* @version v1.8.0 | ||
* Website: https://a11y.nicolas-hoffmann.net/hide-show/ | ||
* License MIT: https://github.com/nico3333fr/jquery-accessible-hide-show-aria/blob/master/LICENSE | ||
*/ | ||
// loading expand paragraphs | ||
// these are recommended settings by a11y experts. You may update to fulfill your needs, but be sure of what you’re doing. | ||
var attr_control = 'data-controls', | ||
attr_expanded = 'aria-expanded', | ||
attr_labelledby = 'data-labelledby', | ||
attr_hidden = 'data-hidden', | ||
$expandmore = $('.js-expandmore'), | ||
$body = $('body'), | ||
delay = 1500, | ||
hash = window.location.hash.replace("#", ""), | ||
multiexpandable = true, | ||
expand_all_text = 'Tout déplier', | ||
collapse_all_text = 'Tout replier'; | ||
var Scampi = Scampi || {}; | ||
if(document.documentElement.classList.contains('no-js')){ | ||
document.documentElement.classList.remove('no-js'); | ||
document.documentElement.classList.add('js'); | ||
} | ||
if ($expandmore.length) { // if there are at least one :) | ||
$expandmore.each(function (index_to_expand) { | ||
var $this = $(this), | ||
index_lisible = index_to_expand + 1, | ||
options = $this.data(), | ||
$hideshow_prefix_classes = typeof options.hideshowPrefixClass !== 'undefined' ? options.hideshowPrefixClass + '-' : '', | ||
$to_expand = $this.next(".js-to_expand"), | ||
$expandmore_text = $this.html(); | ||
Scampi.collapse = function acceDeWebAccordion() { | ||
'use strict'; | ||
$this.html('<button type="button" class="' + $hideshow_prefix_classes + 'expandmore__button js-expandmore-button"><span class="' + $hideshow_prefix_classes + 'expandmore__symbol" aria-hidden="true"></span>' + $expandmore_text + '</button>'); | ||
var $button = $this.children('.js-expandmore-button'); | ||
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); | ||
$to_expand.addClass($hideshow_prefix_classes + 'expandmore__to_expand').stop().delay(delay).queue(function () { | ||
var $this = $(this); | ||
if ($this.hasClass('js-first_load')) { | ||
$this.removeClass('js-first_load'); | ||
} | ||
}); | ||
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } | ||
$button.attr('id', 'label_expand_' + index_lisible); | ||
$button.attr(attr_control, 'expand_' + index_lisible); | ||
$button.attr(attr_expanded, 'false'); | ||
/*eslint no-fallthrough: "off"*/ | ||
var callbackEvents = ['hide', 'show']; | ||
var headersNodeNames = ['H1', 'H2', 'H3', 'H4', 'H5', 'H6']; | ||
$to_expand.attr('id', 'expand_' + index_lisible); | ||
$to_expand.attr(attr_hidden, 'true'); | ||
$to_expand.attr(attr_labelledby, 'label_expand_' + index_lisible); | ||
/** | ||
* Accordion constructor | ||
* @constructor | ||
* @param {Node} el - DOM node | ||
*/ | ||
// quick tip to open (if it has class is-opened or if hash is in expand) | ||
if ($to_expand.hasClass('is-opened') || (hash !== "" && $to_expand.find($("#" + hash)).length)) { | ||
$button.addClass('is-opened').attr(attr_expanded, 'true'); | ||
$to_expand.removeClass('is-opened').removeAttr(attr_hidden); | ||
} | ||
var Accordion = function () { | ||
function Accordion(el) { | ||
_classCallCheck(this, Accordion); | ||
if (!el || !el.nodeName) { | ||
throw new Error('No DOM node provided. Abort.'); | ||
} | ||
}); | ||
this.el = el; | ||
this.multiselectable = this.el.getAttribute('data-multiselectable') === 'true'; | ||
this._accordion = {}; | ||
this._callbacks = {}; | ||
this._handleDisplay = this._handleDisplay.bind(this); | ||
this._handleFocus = this._handleFocus.bind(this); | ||
this._handleHeaders = this._handleHeaders.bind(this); | ||
this._handlePanelFocus = this._handlePanelFocus.bind(this); | ||
this._handlePanel = this._handlePanel.bind(this); | ||
} | ||
/** | ||
* Retrieve first activable header (that does not have `disabled` attribute) | ||
*/ | ||
$body.on('click', '.js-expandmore-button', function (event) { | ||
var $this = $(this), | ||
$destination = $('#' + $this.attr(attr_control)); | ||
if ($this.attr(attr_expanded) === 'false') { | ||
_createClass(Accordion, [{ | ||
key: '_firstActiveHeader', | ||
value: function _firstActiveHeader() { | ||
var activeHeaderIndex = void 0; | ||
if (multiexpandable === false) { | ||
$('.js-expandmore-button').removeClass('is-opened').attr(attr_expanded, 'false'); | ||
$('.js-to_expand').attr(attr_hidden, 'true'); | ||
} | ||
this._accordion.headers.some(function (header, index) { | ||
if (!header.disabled) { | ||
activeHeaderIndex = index; | ||
$this.addClass('is-opened').attr(attr_expanded, 'true'); | ||
$destination.removeAttr(attr_hidden); | ||
} else { | ||
$this.removeClass('is-opened').attr(attr_expanded, 'false'); | ||
$destination.attr(attr_hidden, 'true'); | ||
return true; | ||
} | ||
}); | ||
return activeHeaderIndex; | ||
} | ||
/** | ||
* Toggle display of the panel (show/hide) | ||
* @param {DOMEvent} e - Can be a `MouseEvent` or a `KeyboardEvent` object | ||
*/ | ||
}, { | ||
key: '_handleDisplay', | ||
value: function _handleDisplay(e) { | ||
e.preventDefault(); | ||
var header = e.currentTarget; | ||
if (header.disabled) { | ||
return; | ||
} | ||
event.preventDefault(); | ||
// ensure the header has the focus when a click occurs | ||
if (header !== document.activeElement) { | ||
header.focus(); | ||
} | ||
}); | ||
this._toggleDisplay(this._accordion.headers.indexOf(header)); | ||
} | ||
$body.on('click keydown', '.js-expandmore', function (event) { | ||
var $this = $(this), | ||
$target = $(event.target), | ||
$button_in = $this.find('.js-expandmore-button'); | ||
/** | ||
* Update the current header index before selecting the current panel | ||
* @param {DOMEvent} e - A `FocusEvent` object | ||
*/ | ||
if (!$target.is($button_in) && !$target.closest($button_in).length) { | ||
}, { | ||
key: '_handleFocus', | ||
value: function _handleFocus(e) { | ||
var header = e.currentTarget; | ||
if (event.type === 'click') { | ||
$button_in.trigger('click'); | ||
return false; | ||
if (header.disabled) { | ||
return; | ||
} | ||
this._accordion.currentIndex = this._accordion.headers.indexOf(header); | ||
} | ||
/** | ||
* Handle keystroke on [role=panel] | ||
* @param {DOMEvent} e - A `KeyboardEvent` object | ||
*/ | ||
}, { | ||
key: '_handlePanel', | ||
value: function _handlePanel(e) { | ||
if (this._accordion.currentIndex === undefined) { | ||
this._handlePanelFocus(e); | ||
} | ||
switch (e.keyCode) { | ||
// ctrl + page up | ||
case 33: | ||
if (e.ctrlKey) { | ||
e.preventDefault(); | ||
// focus the previous header | ||
this._switchPanel(this._accordion.currentIndex - 1); | ||
} | ||
if (event.type === 'keydown' && (event.keyCode === 13 || event.keyCode === 32)) { | ||
$button_in.trigger('click'); | ||
return false; | ||
break; | ||
// ctrl + page down | ||
case 34: | ||
if (e.ctrlKey) { | ||
e.preventDefault(); | ||
// focus the next header | ||
this._switchPanel(this._accordion.currentIndex + 1); | ||
} | ||
break; | ||
// focus back to header | ||
// ctrl + up | ||
case 38: | ||
if (e.ctrlKey) { | ||
e.preventDefault(); | ||
// focus linked header | ||
this._switchPanel(this._accordion.currentIndex); | ||
} | ||
break; | ||
} | ||
} | ||
/** | ||
* Ensure that the current header index is the one matching the panel | ||
* @param {DOMEvent} e - A `FocusEvent` or `KeyboardEvent` object | ||
*/ | ||
}); | ||
}, { | ||
key: '_handlePanelFocus', | ||
value: function _handlePanelFocus(e) { | ||
$body.on('click keydown', '.js-expandmore-all', function (event) { | ||
var $this = $(this), | ||
is_expanded = $this.attr('data-expand'), | ||
$all_buttons = $('.js-expandmore-button'), | ||
$all_destinations = $('.js-to_expand'); | ||
if (e.target.doubleFocus) { | ||
e.preventDefault(); | ||
delete e.target.doubleFocus; | ||
if ( | ||
event.type === 'click' || | ||
(event.type === 'keydown' && (event.keyCode === 13 || event.keyCode === 32)) | ||
) { | ||
if (is_expanded === 'true') { | ||
return; | ||
} | ||
$all_buttons.addClass('is-opened').attr(attr_expanded, 'true'); | ||
$all_destinations.removeAttr(attr_hidden); | ||
$this.attr('data-expand', 'false').html(collapse_all_text); | ||
} else { | ||
$all_buttons.removeClass('is-opened').attr(attr_expanded, 'false'); | ||
$all_destinations.attr(attr_hidden, 'true'); | ||
$this.attr('data-expand', 'true').html(expand_all_text); | ||
var panel = e.currentTarget; | ||
this._accordion.currentIndex = this._accordion.panels.indexOf(panel); | ||
// prevent double focus event when the inputs are focused | ||
if (['radio', 'checkbox'].indexOf(e.target.type) >= 0) { | ||
e.target.doubleFocus = true; | ||
} | ||
} | ||
/** | ||
* Handle keystroke on [role=tab] | ||
* @param {DOMEvent} e - A `KeyboardEvent` object | ||
*/ | ||
}, { | ||
key: '_handleHeaders', | ||
value: function _handleHeaders(e) { | ||
if (this._accordion.currentIndex === undefined) { | ||
this._handleFocus(e); | ||
} | ||
switch (e.keyCode) { | ||
// space | ||
case 32: | ||
// return | ||
case 13: | ||
// toggle the display of the linked panel | ||
this._handleDisplay(e); | ||
break; | ||
// end | ||
case 35: | ||
e.preventDefault(); | ||
// focus the last header | ||
this._switchPanel(this._accordion.headers.length - 1); | ||
break; | ||
// home | ||
case 36: | ||
e.preventDefault(); | ||
// focus the first active header | ||
this._switchPanel(this._firstActiveHeader()); | ||
break; | ||
// left | ||
case 37: | ||
// up | ||
case 38: | ||
e.preventDefault(); | ||
// focus the previous header | ||
this._switchPanel(this._accordion.currentIndex - 1); | ||
break; | ||
// right | ||
case 39: | ||
// down | ||
case 40: | ||
e.preventDefault(); | ||
// focus the next header | ||
this._switchPanel(this._accordion.currentIndex + 1); | ||
break; | ||
} | ||
} | ||
/** | ||
* Dummy function | ||
*/ | ||
}, { | ||
key: '_noop', | ||
value: function _noop() {} | ||
/** | ||
* Move the focus to the header based on the index | ||
* @param {number} index - Index of the element to focus | ||
*/ | ||
}, { | ||
key: '_switchPanel', | ||
value: function _switchPanel(index) { | ||
// handle disabled header | ||
if (this._accordion.headers[index] && this._accordion.headers[index].disabled) { | ||
// cycling forward? Then go one item further | ||
var newIndex = index > this._accordion.currentIndex ? index + 1 : index - 1; | ||
this._switchPanel(newIndex); | ||
return; | ||
} | ||
var firstActiveHeader = this._firstActiveHeader(); | ||
if (index < firstActiveHeader) { | ||
this._accordion.currentIndex = this._accordion.headersLength - 1; | ||
} else if (index >= this._accordion.headersLength) { | ||
this._accordion.currentIndex = firstActiveHeader; | ||
} else { | ||
this._accordion.currentIndex = index; | ||
} | ||
this._accordion.headers[this._accordion.currentIndex].focus(); | ||
} | ||
/** | ||
* Toggle the `aria-expanded` attribute on the header based on the passed index | ||
* @param {integer} index - index of the panel | ||
* @param {boolean} show - whether or not display the panel | ||
*/ | ||
}, { | ||
key: '_toggleDisplay', | ||
value: function _toggleDisplay(index, show) { | ||
var header = this._accordion.headers[index]; | ||
var panel = this._accordion.panels[index]; | ||
var headerDisplayed = header.getAttribute('aria-expanded') === 'true'; | ||
if (show === undefined) { | ||
show = header.getAttribute('aria-expanded') === 'false'; | ||
} | ||
if (show && headerDisplayed || !show && !headerDisplayed) { | ||
return; | ||
} | ||
// close the previous header if the accordion doesn't allow multiple panels open | ||
if (show && !this.multiselectable && this._accordion.openedIndexes[0] !== undefined) { | ||
this._toggleDisplay(this._accordion.openedIndexes[0], false); | ||
} | ||
header.setAttribute('aria-expanded', show); | ||
panel[!show ? 'setAttribute' : 'removeAttribute']('hidden', !show); | ||
if (show) { | ||
this._accordion.openedIndexes.push(index); | ||
this._trigger('show', [header, panel]); | ||
} else { | ||
// remove the panel from the list of opened ones | ||
this._accordion.openedIndexes.splice(this._accordion.openedIndexes.indexOf(index), 1); | ||
this._trigger('hide', [header, panel]); | ||
} | ||
} | ||
}, { | ||
key: '_trigger', | ||
value: function _trigger(eventName, params) { | ||
var _this = this; | ||
if (!this._callbacks[eventName]) { | ||
return; | ||
} | ||
this._callbacks[eventName].forEach(function (callback) { | ||
callback.apply(_this, params); | ||
}); | ||
} | ||
}, { | ||
key: 'closeAll', | ||
value: function closeAll() { | ||
var _this2 = this; | ||
this._accordion.panels.forEach(function (panel, index) { | ||
_this2._toggleDisplay(index, false); | ||
}); | ||
} | ||
}, { | ||
key: 'close', | ||
value: function close(panel) { | ||
var index = this._accordion.panels.indexOf(panel); | ||
this._toggleDisplay(index, false); | ||
} | ||
/** | ||
* Parse the accordion children to setup the headers and panels elements | ||
*/ | ||
}, { | ||
key: 'mount', | ||
value: function mount() { | ||
var _this3 = this; | ||
// create reference arrays | ||
this._accordion.headers = []; | ||
this._accordion.panels = []; | ||
this._accordion.openedIndexes = []; | ||
// loop on each headers elements to find panel elements and update their attributes | ||
Array.prototype.slice.call(this.el.children).forEach(function (header, index) { | ||
var isHeader = headersNodeNames.indexOf(header.nodeName) > -1; | ||
// skip non header child | ||
if (!isHeader && header.getAttribute('role') !== 'heading' && !header.hasAttribute('aria-level')) { | ||
return; | ||
} | ||
// set the header to be the button actioning the panel | ||
header = header.querySelector('button[aria-controls], button[data-controls], [role="button"][aria-controls], [role="button"][data-controls]'); | ||
if (!header) { | ||
return; | ||
} | ||
var id = header.getAttribute('aria-controls') || header.getAttribute('data-controls'); | ||
var panel = document.getElementById(id); | ||
var openedTab = false; | ||
if (!panel) { | ||
throw new Error('Could not find associated panel for header ' + header.id + '. Use [aria-controls="panelId"] or [data-controls="panelId"] on the [role="header"] element to link them together'); | ||
} | ||
// store the header and the panel on their respective arrays on the headerlist | ||
_this3._accordion.headers.push(header); | ||
_this3._accordion.panels.push(panel); | ||
header.disabled = header.hasAttribute('disabled') || header.getAttribute('aria-disabled') === 'true'; | ||
if (header.getAttribute('data-expand') === 'true' && !header.disabled) { | ||
if (_this3.multiselectable || !_this3.multiselectable && !_this3._accordion.openedIndexes.length) { | ||
_this3._toggleDisplay(_this3._accordion.headers.length - 1, true); | ||
openedTab = true; | ||
} | ||
} | ||
// remove setup data attributes | ||
header.removeAttribute('data-expand'); | ||
// set the attributes according the the openedTab status | ||
header.setAttribute('tabindex', 0); | ||
header.setAttribute('aria-expanded', openedTab); | ||
panel[!openedTab ? 'setAttribute' : 'removeAttribute' ]('hidden', !openedTab); | ||
// subscribe internal events for header and panel | ||
header.addEventListener('click', _this3._handleDisplay); | ||
header.addEventListener('focus', _this3._handleFocus); | ||
header.addEventListener('keydown', _this3._handleHeaders); | ||
panel.addEventListener('focus', _this3._handlePanelFocus, true); | ||
panel.addEventListener('keydown', _this3._handlePanel); | ||
}); | ||
// store constants | ||
this._accordion.headersLength = this._accordion.headers.length; | ||
this._accordion.panelsLength = this._accordion.panels.length; | ||
} | ||
}, { | ||
key: 'off', | ||
value: function off(event, callback) { | ||
if (!this._callbacks[event]) { | ||
return; | ||
} | ||
var callbackIndex = this._callbacks[event].indexOf(callback); | ||
if (callbackIndex < 0) { | ||
return; | ||
} | ||
this._callbacks[event].splice(callbackIndex, 1); | ||
} | ||
}, { | ||
key: 'on', | ||
value: function on(event, callback) { | ||
if (callbackEvents.indexOf(event) < 0) { | ||
return; | ||
} | ||
if (!this._callbacks[event]) { | ||
this._callbacks[event] = []; | ||
} | ||
this._callbacks[event].push(callback); | ||
} | ||
}, { | ||
key: 'openAll', | ||
value: function openAll() { | ||
var _this4 = this; | ||
if (!this.multiselectable) { | ||
return; | ||
} | ||
this._accordion.panels.forEach(function (panel, index) { | ||
_this4._toggleDisplay(index, true); | ||
}); | ||
} | ||
}, { | ||
key: 'open', | ||
value: function open(panel) { | ||
var index = this._accordion.panels.indexOf(panel); | ||
this._toggleDisplay(index, true); | ||
} | ||
/** | ||
* Returns an array of opened panels | ||
*/ | ||
}, { | ||
key: 'unmount', | ||
/** | ||
* unbind accordion | ||
*/ | ||
value: function unmount() { | ||
var _this5 = this; | ||
this._accordion.headers.forEach(function (header, index) { | ||
var panel = _this5._accordion.panels[index]; | ||
// unsubscribe internal events for header and panel | ||
header.removeEventListener('click', _this5._handleDisplay); | ||
header.removeEventListener('focus', _this5._handleFocus); | ||
header.removeEventListener('keydown', _this5._handleHeaders); | ||
header.removeAttribute('tabindex'); | ||
header.removeAttribute('aria-expanded'); | ||
panel.removeEventListener('focus', _this5._handlePanelFocus, true); | ||
panel.removeEventListener('keydown', _this5._handlePanel); | ||
panel.removeAttribute('hidden'); | ||
}); | ||
} | ||
}, { | ||
key: 'current', | ||
get: function get() { | ||
var _this6 = this; | ||
return this._accordion.openedIndexes.map(function (index) { | ||
return { | ||
header: _this6._accordion.headers[index], | ||
panel: _this6._accordion.panels[index] | ||
}; | ||
}); | ||
} | ||
} | ||
]); | ||
return Accordion; | ||
}(); | ||
var expandContainer = document.querySelectorAll('.js-expandmore'); | ||
var expandAll = document.querySelectorAll('.js-expandmore-all'); | ||
var accordeons = Array.prototype.map.call(expandContainer, function(el, index){ | ||
var expandTitle = el.querySelectorAll('.js-expandmore-title'); | ||
var expandPanel = el.querySelectorAll('.js-expandmore-panel'); | ||
Array.prototype.forEach.call(expandTitle, function(title, titleIndex){ | ||
var innerTitle = title.innerHTML; | ||
var id = 'panel-' + (title.id || '' + index + titleIndex); | ||
title.innerHTML = '<button type="button" class="expandmore__button js-expandmore-button" data-controls="' + id + '"><span class="expandmore__symbol" aria-hidden="true"></span>' + innerTitle + '</button>'; | ||
if(title.hasAttribute('data-expand')){ | ||
title.firstElementChild.setAttribute('data-expand', 'true'); | ||
title.removeAttribute('data-expand'); | ||
} | ||
expandPanel[titleIndex].id = id; | ||
expandPanel[titleIndex].setAttribute('hidden', true); | ||
}); | ||
var accordion = new Accordion(el); | ||
}); | ||
el.scampiCollapse = accordion; | ||
accordion.mount(); | ||
return accordion; | ||
}); | ||
function toggleAll(expand){ | ||
Array.prototype.forEach.call(expandAll, function(el){ | ||
accordeons.forEach(function(accordeon){ | ||
accordeon[expand ? 'openAll' : 'closeAll'](); | ||
}); | ||
el.setAttribute('data-expand', !expand); | ||
el.innerText = el.getAttribute(expand ? 'data-collapsetext' : 'data-expandtext'); | ||
}); | ||
} | ||
function togglePanel(button, collapseEl, expand){ | ||
var collapse = collapseEl.scampiCollapse; | ||
collapse[expand ? 'openAll' : 'closeAll'](); | ||
button.setAttribute('data-expand', !expand); | ||
button.innerText = button.getAttribute(expand ? 'data-collapsetext' : 'data-expandtext'); | ||
} | ||
Array.prototype.forEach.call(expandAll, function(el){ | ||
el.addEventListener('click', function(){ | ||
var expand = el.getAttribute('data-expand') === 'true'; | ||
var controls = el.getAttribute('data-controls') | ||
var collapseEl = document.getElementById(controls); | ||
if(collapseEl){ | ||
togglePanel(el, collapseEl, expand); | ||
} else { | ||
toggleAll(expand); | ||
} | ||
}); | ||
}); | ||
return accordeons; | ||
}(); |
@@ -8,3 +8,3 @@ # Fontface | ||
Mise en place | ||
Utilisation | ||
------------- | ||
@@ -18,15 +18,17 @@ | ||
**IMPORTANT :** il est essentiel d'importer ce module au tout début de la feuille de style, juste après l'import des settings du projet. | ||
### Settings | ||
### Configuration | ||
Dans le fichier des settings du projet : | ||
Les variables proposées dans ce module sont : | ||
1. passer le setting `$enable-fontface` à `true`. | ||
2. déclarer la "map" des fontes utilisées | ||
3. définir le chemin vers le répertoire des webfontes | ||
4. créer des noms de variables correspondant à chaque font-stack. | ||
5. (facultatif) donner des noms génériques à ces font-stacks | ||
- `$enable-fontface` : activation du module, sa valeur par défaut est `false` | ||
- `$font-map` : déclaration de la font map, vide par défaut | ||
- `$webfont-path` : chemin vers le répertoire contenant les webfonts, sa valeur par défaut est `../webfonts/` | ||
#### Exemple | ||
Note : Il faut également créer des noms de variables correspondant à chaque font-stack et éventuellement donner des noms génériques à ces font-stacks. | ||
Exemple d'utilisation | ||
------------- | ||
````sass | ||
@@ -85,5 +87,1 @@ | ||
```` | ||
### Import du module | ||
**IMPORTANT :** il est essentiel d'importer ce module au tout début de la feuille de style, juste après l'import des settings du projet. |
@@ -15,4 +15,9 @@ # Fonticon | ||
Placer la fonte dans le répertoire des webfonts, modifier la variable du chemin pour indiquer son emplacement. | ||
### Configuration | ||
La variable proposée dans ce module est : | ||
- `$fonticon-path` : chemin vers le répertoire contenant les fonticons, sa valeur par défaut est `../webfonts/fonticon/` | ||
### Accessibilité | ||
@@ -19,0 +24,0 @@ |
@@ -36,3 +36,3 @@ # Forms-inline | ||
<label class="form-check-label" for="inlineFormCheck"> | ||
Cochez moi | ||
Cochez-moi | ||
</label> | ||
@@ -39,0 +39,0 @@ </div> |
@@ -28,2 +28,19 @@ # Forms | ||
### Configuration | ||
Les variables proposées dans ce module sont : | ||
- `$input-btn-border-width` : bordure des boutons, sa valeur par défaut est `$border-width`; | ||
- `$input-padding-x` : espacement horizontal des input, sa valeur par défaut est `.75em`; | ||
- `$input-padding-y` : espacement vertical des input, sa valeur par défaut est `.5em`; | ||
- `$input-bg` : couleur de fond des input, sa valeur par défaut est `#fff`; | ||
- `$input-color` : couleur du texte des input, sa valeur par défaut est `$gray`; | ||
- `$input-border-color` : couleur de la bordure des input, sa valeur par défaut est `#ccc`; | ||
- `$input-box-shadow` : ombrage des input, sa valeur par défaut est `inset 0 1px 1px rgba(0,0,0,.075)`; | ||
- `$input-border-radius` : border-radius des input, sa valeur par défaut est `$border-radius`; | ||
- `$input-placeholder-color` : couleur du placeholder, sa valeur par défaut est `#999 !default`; | ||
- `$input-height` : hauteur de l'input, sa valeur par défaut est `(($font-size-base * $line-height) + ($input-padding-y * 2))`; | ||
De nombreuses autres variables personnalisables sont décrites dans le fichier [modules/forms/_index.scss](https://gitlab.com/pidila/scampi/blob/master/modules/forms/_index.scss). | ||
Documentation des feuilles de style complétant `_styles-forms.scss` : | ||
@@ -30,0 +47,0 @@ |
// menu-simple | ||
// thanks to http://www.a11ymatters.com/pattern/mobile-nav/ | ||
$(document).ready(function(){ | ||
var Scampi = Scampi || {}; | ||
var toggle = document.querySelector('#toggle-menu'); | ||
var menu = document.querySelector('.nav-main-list'); | ||
var menuItems = document.querySelectorAll('.nav-main-list li a'); | ||
if(document.documentElement.classList.contains('no-js')){ | ||
document.documentElement.classList.remove('no-js'); | ||
document.documentElement.classList.add('js'); | ||
} | ||
toggle.addEventListener('click', function(){ | ||
if (menu.classList.contains('is-open')) { | ||
this.setAttribute('aria-expanded', 'false'); | ||
menu.classList.remove('is-open'); | ||
} else { | ||
menu.classList.add('is-open'); | ||
this.setAttribute('aria-expanded', 'true'); | ||
} | ||
}); | ||
}); | ||
Scampi.menuSimple = function menuSimple(){ | ||
var toggle = document.querySelector('#toggle-menu'); | ||
var menu = document.querySelector('.nav-main-list'); | ||
if(!toggle){ | ||
return; | ||
} | ||
toggle.addEventListener('click', function(){ | ||
if (menu.classList.contains('is-open')) { | ||
toggle.setAttribute('aria-expanded', 'false'); | ||
menu.classList.remove('is-open'); | ||
} else { | ||
menu.classList.add('is-open'); | ||
toggle.setAttribute('aria-expanded', 'true'); | ||
} | ||
}); | ||
}; | ||
Scampi.menuSimple(); |
@@ -10,6 +10,3 @@ # Menu-simple | ||
### Accessibilité | ||
Des attributs aria sur le bouton et le menu permettront aux utilisateurs pilotant une aide technique d’être informés de la nature de ces éléments. | ||
Utilisation | ||
@@ -29,3 +26,28 @@ ----------- | ||
### Configuration | ||
Les variables proposées dans ce module sont : | ||
- `$enable-fontface` : activation du module, sa valeur par défaut est `false` | ||
#### Desktop | ||
- `$menu-simple-bg-color` : couleur de fond du menu, sa valeur par défaut est `$primary-color` | ||
- `$menu-simple-text-color` : couleur du texte du menu, sa valeur par défaut est `#fff` | ||
- `$menu-simple-bg-color-hover` : couleur de fond du menu au survol, sa valeur par défaut est `#000` | ||
- `$menu-simple-text-color-hover` : couleur du texte du menu au survol, sa valeur par défaut est `#fff` | ||
- `$menu-simple-bg-color-open` : couleur de fond du menu actif, sa valeur par défaut est `#000` | ||
- `$menu-simple-text-color-open` : couleur du texte du menu actif, sa valeur par défaut est `#fff` | ||
#### Mobile | ||
- `$toggle-menu-bg-color` : couleur de fond du menu, sa valeur par défaut est `$primary-color` | ||
- `$toggle-menu-text-color` : couleur du texte du menu, sa valeur par défaut est `#fff` | ||
- `$toggle-menu-bg-color-hover` : couleur de fond du menu au survol, sa valeur par défaut est `#000` | ||
- `$toggle-menu-text-color-hover` : couleur du texte du menu au survol, sa valeur par défaut est `#fff` | ||
- `$toggle-menu-bg-color-open` : couleur de fond du menu ouvert, sa valeur par défaut est `$gray-2` | ||
- `$toggle-menu-text-color-open` : couleur de fond du menu ouvert, sa valeur par défaut est `#fff` | ||
### Accessibilité | ||
Des attributs aria sur le bouton et le menu permettront aux utilisateurs pilotant une aide technique d’être informés de la nature de ces éléments. | ||
### Script associé | ||
@@ -35,5 +57,2 @@ | ||
Note : copier le script présent dans le module à l’endroit où sont rangés les autres scripts (en principe scripts/main/). | ||
Exemple | ||
@@ -40,0 +59,0 @@ ------- |
@@ -1,490 +0,406 @@ | ||
/* ======================================================================== | ||
* Bootstrap: modal.js v3.3.7 | ||
* http://getbootstrap.com/javascript/#modals | ||
* ======================================================================== | ||
* Copyright 2011-2016 Twitter, Inc. | ||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) | ||
* ======================================================================== */ | ||
/* | ||
* ES2015 accessible modal window system, using ARIA | ||
* Website: https://van11y.net/accessible-modal/ | ||
* License MIT: https://github.com/nico3333fr/van11y-accessible-modal-window-aria/blob/master/LICENSE | ||
*/ | ||
var Scampi = Scampi || {}; | ||
+function ($) { | ||
'use strict'; | ||
if(document.documentElement.classList.contains('no-js')){ | ||
document.documentElement.classList.remove('no-js'); | ||
document.documentElement.classList.add('js'); | ||
} | ||
// MODAL CLASS DEFINITION | ||
// ====================== | ||
Scampi.modal = function modal() { | ||
(function (doc) { | ||
var Modal = function (element, options) { | ||
this.options = options | ||
this.$body = $(document.body) | ||
this.$element = $(element) | ||
this.$dialog = this.$element.find('.modal-dialog') | ||
this.$backdrop = null | ||
this.isShown = null | ||
this.originalBodyPad = null | ||
this.scrollbarWidth = 0 | ||
this.ignoreBackdropClick = false | ||
'use strict'; | ||
if (this.options.remote) { | ||
this.$element | ||
.find('.modal-content') | ||
.load(this.options.remote, $.proxy(function () { | ||
this.$element.trigger('loaded.bs.modal') | ||
}, this)) | ||
} | ||
} | ||
var MODAL_JS_CLASS = 'js-modal'; | ||
var MODAL_ID_PREFIX = 'label_modal_'; | ||
var MODAL_CLASS_SUFFIX = 'modal'; | ||
var MODAL_DATA_BACKGROUND_ATTR = 'data-modal-background-click'; | ||
var MODAL_PREFIX_CLASS_ATTR = 'data-modal-prefix-class'; | ||
var MODAL_TEXT_ATTR = 'data-modal-text'; | ||
var MODAL_CONTENT_ID_ATTR = 'data-modal-content-id'; | ||
var MODAL_DESCRIBEDBY_ID_ATTR = 'data-modal-describedby-id'; | ||
var MODAL_TITLE_ATTR = 'data-modal-title'; | ||
var MODAL_FOCUS_TO_ATTR = 'data-modal-focus-toid'; | ||
var MODAL_CLOSE_TEXT_ATTR = 'data-modal-close-text'; | ||
var MODAL_CLOSE_TITLE_ATTR = 'data-modal-close-title'; | ||
var MODAL_CLOSE_IMG_ATTR = 'data-modal-close-img'; | ||
var MODAL_ROLE = 'dialog'; | ||
Modal.VERSION = '3.3.7' | ||
var MODAL_BUTTON_CLASS_SUFFIX = 'modal-close btn btn-secondary'; | ||
var MODAL_BUTTON_JS_ID = 'js-modal-close'; | ||
var MODAL_BUTTON_JS_CLASS = 'js-modal-close'; | ||
var MODAL_BUTTON_CONTENT_BACK_ID = 'data-content-back-id'; | ||
var MODAL_BUTTON_FOCUS_BACK_ID = 'data-focus-back'; | ||
Modal.TRANSITION_DURATION = 300 | ||
Modal.BACKDROP_TRANSITION_DURATION = 150 | ||
var MODAL_WRAPPER_CLASS_SUFFIX = 'modal__wrapper'; | ||
var MODAL_CONTENT_CLASS_SUFFIX = 'modal__content'; | ||
var MODAL_CONTENT_JS_ID = 'js-modal-content'; | ||
Modal.DEFAULTS = { | ||
backdrop: true, | ||
keyboard: true, | ||
show: true | ||
} | ||
var MODAL_CLOSE_IMG_CLASS_SUFFIX = 'modal__closeimg'; | ||
var MODAL_CLOSE_TEXT_CLASS_SUFFIX = 'modal-close__text'; | ||
Modal.prototype.toggle = function (_relatedTarget) { | ||
return this.isShown ? this.hide() : this.show(_relatedTarget) | ||
} | ||
var MODAL_TITLE_ID = 'modal-title'; | ||
var MODAL_TITLE_CLASS_SUFFIX = 'modal-title'; | ||
Modal.prototype.show = function (_relatedTarget) { | ||
var that = this | ||
var e = $.Event('show.bs.modal', { relatedTarget: _relatedTarget }) | ||
var FOCUSABLE_ELEMENTS_STRING = "a[href], area[href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), button:not([disabled]), iframe, object, embed, *[tabindex], *[contenteditable]"; | ||
var WRAPPER_PAGE_JS = 'js-modal-page'; | ||
this.$element.trigger(e) | ||
var MODAL_JS_ID = 'js-modal'; | ||
if (this.isShown || e.isDefaultPrevented()) return | ||
var MODAL_OVERLAY_ID = 'js-modal-overlay'; | ||
var MODAL_OVERLAY_CLASS_SUFFIX = 'modal-overlay'; | ||
var MODAL_OVERLAY_TXT = 'Close modal'; | ||
var MODAL_OVERLAY_BG_ENABLED_ATTR = 'data-background-click'; | ||
this.isShown = true | ||
var VISUALLY_HIDDEN_CLASS = 'invisible'; | ||
var NO_SCROLL_CLASS = 'no-scroll'; | ||
this.checkScrollbar() | ||
this.setScrollbar() | ||
this.$body.addClass('modal-open') | ||
var ATTR_ROLE = 'role'; | ||
var ATTR_OPEN = 'open'; | ||
var ATTR_LABELLEDBY = 'aria-labelledby'; | ||
var ATTR_DESCRIBEDBY = 'aria-describedby'; | ||
var ATTR_HIDDEN = 'aria-hidden'; | ||
//const ATTR_MODAL = 'aria-modal="true"'; | ||
var ATTR_HASPOPUP = 'aria-haspopup'; | ||
var ATTR_HASPOPUP_VALUE = 'dialog'; | ||
this.escape() | ||
this.resize() | ||
var findById = function findById(id) { | ||
return doc.getElementById(id); | ||
}; | ||
this.$element.on('click.dismiss.bs.modal', '[data-dismiss="modal"]', $.proxy(this.hide, this)) | ||
var addClass = function addClass(el, className) { | ||
if (el.classList) { | ||
el.classList.add(className); // IE 10+ | ||
} else { | ||
el.className += ' ' + className; // IE 8+ | ||
} | ||
}; | ||
this.$dialog.on('mousedown.dismiss.bs.modal', function () { | ||
that.$element.one('mouseup.dismiss.bs.modal', function (e) { | ||
if ($(e.target).is(that.$element)) that.ignoreBackdropClick = true | ||
}) | ||
}) | ||
var removeClass = function removeClass(el, className) { | ||
if (el.classList) { | ||
el.classList.remove(className); // IE 10+ | ||
} else { | ||
el.className = el.className.replace(new RegExp('(^|\\b)' + className.split(' ').join('|') + '(\\b|$)', 'gi'), ' '); // IE 8+ | ||
} | ||
}; | ||
this.backdrop(function () { | ||
var transition = $.support.transition && that.$element.hasClass('fade') | ||
var hasClass = function hasClass(el, className) { | ||
if (el.classList) { | ||
return el.classList.contains(className); // IE 10+ | ||
} else { | ||
return new RegExp('(^| )' + className + '( |$)', 'gi').test(el.className); // IE 8+ ? | ||
} | ||
}; | ||
/*const wrapInner = (el, wrapper_el) => { // doesn't work on IE/Edge, f… | ||
while (el.firstChild) | ||
wrapper_el.append(el.firstChild); | ||
el.append(wrapper_el); | ||
}*/ | ||
function wrapInner(parent, wrapper) { | ||
if (typeof wrapper === "string") wrapper = document.createElement(wrapper); | ||
if (!that.$element.parent().length) { | ||
that.$element.appendTo(that.$body) // don't move modals dom position | ||
} | ||
parent.appendChild(wrapper); | ||
that.$element | ||
.show() | ||
.scrollTop(0) | ||
while (parent.firstChild !== wrapper) wrapper.appendChild(parent.firstChild); | ||
} | ||
that.adjustDialog() | ||
function remove(el) { | ||
/* node.remove() is too modern for IE≤11 */ | ||
el.parentNode.removeChild(el); | ||
} | ||
if (transition) { | ||
that.$element[0].offsetWidth // force reflow | ||
/* gets an element el, search if it is child of parent class, returns id of the parent */ | ||
var searchParent = function searchParent(el, parentClass) { | ||
var found = false; | ||
var parentElement = el.parentNode; | ||
while (parentElement && found === false) { | ||
if (hasClass(parentElement, parentClass) === true) { | ||
found = true; | ||
} else { | ||
parentElement = parentElement.parentNode; | ||
} | ||
} | ||
if (found === true) { | ||
return parentElement.getAttribute('id'); | ||
} else { | ||
return ''; | ||
} | ||
}; | ||
that.$element | ||
.addClass('in') | ||
.attr('aria-hidden', false) | ||
/** | ||
* Create the template for an overlay | ||
* @param {Object} config | ||
* @return {String} | ||
*/ | ||
var createOverlay = function createOverlay(config) { | ||
that.enforceFocus() | ||
var id = MODAL_OVERLAY_ID; | ||
var overlayText = config.text || MODAL_OVERLAY_TXT; | ||
var overlayClass = config.prefixClass + MODAL_OVERLAY_CLASS_SUFFIX; | ||
var overlayBackgroundEnabled = config.backgroundEnabled === 'disabled' ? 'disabled' : 'enabled'; | ||
var e = $.Event('shown.bs.modal', { relatedTarget: _relatedTarget }) | ||
return '<span\n id="' + id + '"\n class="' + overlayClass + '"\n ' + MODAL_OVERLAY_BG_ENABLED_ATTR + '="' + overlayBackgroundEnabled + '"\n title="' + overlayText + '"\n >\n <span class="' + VISUALLY_HIDDEN_CLASS + '">' + overlayText + '</span>\n </span>'; | ||
}; | ||
transition ? | ||
that.$dialog // wait for modal to slide in | ||
.one('bsTransitionEnd', function () { | ||
that.$element.trigger('focus').trigger(e) | ||
}) | ||
.emulateTransitionEnd(Modal.TRANSITION_DURATION) : | ||
that.$element.trigger('focus').trigger(e) | ||
}) | ||
} | ||
/** | ||
* Create the template for a modal | ||
* @param {Object} config | ||
* @return {String} | ||
*/ | ||
var createModal = function createModal(config) { | ||
Modal.prototype.hide = function (e) { | ||
if (e) e.preventDefault() | ||
var id = MODAL_JS_ID; | ||
var modalClassName = config.modalPrefixClass + MODAL_CLASS_SUFFIX; | ||
var modalClassWrapper = config.modalPrefixClass + MODAL_WRAPPER_CLASS_SUFFIX; | ||
var buttonCloseClassName = config.modalPrefixClass + MODAL_BUTTON_CLASS_SUFFIX; | ||
var buttonCloseInner = config.modalCloseImgPath ? '<img src="' + config.modalCloseImgPath + '" alt="' + config.modalCloseText + '" class="' + config.modalPrefixClass + MODAL_CLOSE_IMG_CLASS_SUFFIX + '" />' : '<span class="' + config.modalPrefixClass + MODAL_CLOSE_TEXT_CLASS_SUFFIX + '">\n ' + config.modalCloseText + '\n </span>'; | ||
var contentClassName = config.modalPrefixClass + MODAL_CONTENT_CLASS_SUFFIX; | ||
var titleClassName = config.modalPrefixClass + MODAL_TITLE_CLASS_SUFFIX; | ||
var title = config.modalTitle !== '' ? '<h1 id="' + MODAL_TITLE_ID + '" class="' + titleClassName + '">\n ' + config.modalTitle + '\n </h1>' : ''; | ||
var button_close = '<button type="button" class="' + MODAL_BUTTON_JS_CLASS + ' ' + buttonCloseClassName + '" id="' + MODAL_BUTTON_JS_ID + '" title="' + config.modalCloseTitle + '" ' + MODAL_BUTTON_CONTENT_BACK_ID + '="' + config.modalContentId + '" ' + MODAL_BUTTON_FOCUS_BACK_ID + '="' + config.modalFocusBackId + '">\n ' + buttonCloseInner + '\n </button>'; | ||
var content = config.modalText; | ||
var describedById = config.modalDescribedById !== '' ? ATTR_DESCRIBEDBY + '="' + config.modalDescribedById + '"' : ''; | ||
e = $.Event('hide.bs.modal') | ||
// If there is no content but an id we try to fetch content id | ||
if (content === '' && config.modalContentId) { | ||
var contentFromId = findById(config.modalContentId); | ||
if (contentFromId) { | ||
content = '<div id="' + MODAL_CONTENT_JS_ID + '">\n ' + contentFromId.innerHTML + '\n </div'; | ||
// we remove content from its source to avoid id duplicates, etc. | ||
contentFromId.innerHTML = ''; | ||
} | ||
} | ||
this.$element.trigger(e) | ||
return '<dialog id="' + id + '" class="' + modalClassName + '" ' + ATTR_ROLE + '="' + MODAL_ROLE + '" ' + describedById + ' ' + ATTR_OPEN + ' ' + ATTR_LABELLEDBY + '="' + MODAL_TITLE_ID + '">\n <div role="document" class="' + modalClassWrapper + '">\n ' + button_close + '\n <div class="' + contentClassName + '">\n ' + title + '\n ' + content + '\n </div>\n </div>\n </dialog>'; | ||
}; | ||
if (!this.isShown || e.isDefaultPrevented()) return | ||
var closeModal = function closeModal(config) { | ||
this.isShown = false | ||
remove(config.modal); | ||
remove(config.overlay); | ||
this.escape() | ||
this.resize() | ||
$(document).off('focusin.bs.modal') | ||
this.$element | ||
.removeClass('in') | ||
.attr('aria-hidden', true) | ||
.off('click.dismiss.bs.modal') | ||
.off('mouseup.dismiss.bs.modal') | ||
this.$dialog.off('mousedown.dismiss.bs.modal') | ||
$.support.transition && this.$element.hasClass('fade') ? | ||
this.$element | ||
.one('bsTransitionEnd', $.proxy(this.hideModal, this)) | ||
.emulateTransitionEnd(Modal.TRANSITION_DURATION) : | ||
this.hideModal() | ||
} | ||
Modal.prototype.enforceFocus = function () { | ||
$(document) | ||
.off('focusin.bs.modal') // guard against infinite focus loop | ||
.on('focusin.bs.modal', $.proxy(function (e) { | ||
if (document !== e.target && | ||
this.$element[0] !== e.target && | ||
!this.$element.has(e.target).length) { | ||
this.$element.trigger('focus') | ||
if (config.contentBackId !== '') { | ||
var contentBack = findById(config.contentBackId); | ||
if (contentBack) { | ||
contentBack.innerHTML = config.modalContent; | ||
} | ||
}, this)) | ||
} | ||
} | ||
Modal.prototype.escape = function () { | ||
if (this.isShown && this.options.keyboard) { | ||
this.$element.on('keydown.dismiss.bs.modal', $.proxy(function (e) { | ||
e.which == 27 && this.hide() | ||
}, this)) | ||
} else if (!this.isShown) { | ||
this.$element.off('keydown.dismiss.bs.modal') | ||
} | ||
} | ||
Modal.prototype.resize = function () { | ||
if (this.isShown) { | ||
$(window).on('resize.bs.modal', $.proxy(this.handleUpdate, this)) | ||
} else { | ||
$(window).off('resize.bs.modal') | ||
} | ||
} | ||
Modal.prototype.hideModal = function () { | ||
var that = this | ||
this.$element.hide() | ||
this.backdrop(function () { | ||
that.$body.removeClass('modal-open') | ||
that.resetAdjustments() | ||
that.resetScrollbar() | ||
that.$element.trigger('hidden.bs.modal') | ||
}) | ||
} | ||
Modal.prototype.removeBackdrop = function () { | ||
this.$backdrop && this.$backdrop.remove() | ||
this.$backdrop = null | ||
} | ||
Modal.prototype.backdrop = function (callback) { | ||
var that = this | ||
var animate = this.$element.hasClass('fade') ? 'fade' : '' | ||
if (this.isShown && this.options.backdrop) { | ||
var doAnimate = $.support.transition && animate | ||
this.$backdrop = $(document.createElement('div')) | ||
.addClass('modal-backdrop ' + animate) | ||
.appendTo(this.$body) | ||
this.$element.on('click.dismiss.bs.modal', $.proxy(function (e) { | ||
if (this.ignoreBackdropClick) { | ||
this.ignoreBackdropClick = false | ||
return | ||
if (config.modalFocusBackId) { | ||
var contentFocus = findById(config.modalFocusBackId); | ||
if (contentFocus) { | ||
contentFocus.focus(); | ||
} | ||
if (e.target !== e.currentTarget) return | ||
this.options.backdrop == 'static' | ||
? this.$element[0].focus() | ||
: this.hide() | ||
}, this)) | ||
if (doAnimate) this.$backdrop[0].offsetWidth // force reflow | ||
this.$backdrop | ||
.addClass('in') | ||
.attr('aria-hidden', false) | ||
if (!callback) return | ||
doAnimate ? | ||
this.$backdrop | ||
.one('bsTransitionEnd', callback) | ||
.emulateTransitionEnd(Modal.BACKDROP_TRANSITION_DURATION) : | ||
callback() | ||
} else if (!this.isShown && this.$backdrop) { | ||
this.$backdrop | ||
.removeClass('in') | ||
.attr('aria-hidden', true) | ||
var callbackRemove = function () { | ||
that.removeBackdrop() | ||
callback && callback() | ||
} | ||
$.support.transition && this.$element.hasClass('fade') ? | ||
this.$backdrop | ||
.one('bsTransitionEnd', callbackRemove) | ||
.emulateTransitionEnd(Modal.BACKDROP_TRANSITION_DURATION) : | ||
callbackRemove() | ||
}; | ||
} else if (callback) { | ||
callback() | ||
} | ||
} | ||
/** Find all modals inside a container | ||
* @param {Node} node Default document | ||
* @return {Array} | ||
*/ | ||
var $listModals = function $listModals() { | ||
var node = arguments.length <= 0 || arguments[0] === undefined ? doc : arguments[0]; | ||
return [].slice.call(node.querySelectorAll('.' + MODAL_JS_CLASS)); | ||
}; | ||
// these following methods are used to handle overflowing modals | ||
/** | ||
* Build modals for a container | ||
* @param {Node} node | ||
*/ | ||
var attach = function attach(node) { | ||
var addListeners = arguments.length <= 1 || arguments[1] === undefined ? true : arguments[1]; | ||
Modal.prototype.handleUpdate = function () { | ||
this.adjustDialog() | ||
} | ||
$listModals(node).forEach(function (modal_node) { | ||
Modal.prototype.adjustDialog = function () { | ||
var modalIsOverflowing = this.$element[0].scrollHeight > document.documentElement.clientHeight | ||
var iLisible = Math.random().toString(32).slice(2, 12); | ||
var wrapperBody = findById(WRAPPER_PAGE_JS); | ||
var body = doc.querySelector('body'); | ||
this.$element.css({ | ||
paddingLeft: !this.bodyIsOverflowing && modalIsOverflowing ? this.scrollbarWidth : '', | ||
paddingRight: this.bodyIsOverflowing && !modalIsOverflowing ? this.scrollbarWidth : '' | ||
}) | ||
} | ||
modal_node.setAttribute('id', MODAL_ID_PREFIX + iLisible); | ||
modal_node.setAttribute(ATTR_HASPOPUP, ATTR_HASPOPUP_VALUE); | ||
Modal.prototype.resetAdjustments = function () { | ||
this.$element.css({ | ||
paddingLeft: '', | ||
paddingRight: '' | ||
}) | ||
} | ||
if (wrapperBody === null || wrapperBody.length === 0) { | ||
var wrapper = doc.createElement('DIV'); | ||
wrapper.setAttribute('id', WRAPPER_PAGE_JS); | ||
wrapInner(body, wrapper); | ||
} | ||
}); | ||
Modal.prototype.checkScrollbar = function () { | ||
var fullWindowWidth = window.innerWidth | ||
if (!fullWindowWidth) { // workaround for missing window.innerWidth in IE8 | ||
var documentElementRect = document.documentElement.getBoundingClientRect() | ||
fullWindowWidth = documentElementRect.right - Math.abs(documentElementRect.left) | ||
} | ||
this.bodyIsOverflowing = document.body.clientWidth < fullWindowWidth | ||
this.scrollbarWidth = this.measureScrollbar() | ||
} | ||
if (addListeners) { | ||
Modal.prototype.setScrollbar = function () { | ||
var bodyPad = parseInt((this.$body.css('padding-right') || 0), 10) | ||
this.originalBodyPad = document.body.style.paddingRight || '' | ||
if (this.bodyIsOverflowing) this.$body.css('padding-right', bodyPad + this.scrollbarWidth) | ||
} | ||
/* listeners */ | ||
['click', 'keydown'].forEach(function (eventName) { | ||
Modal.prototype.resetScrollbar = function () { | ||
this.$body.css('padding-right', this.originalBodyPad) | ||
} | ||
doc.body.addEventListener(eventName, function (e) { | ||
Modal.prototype.measureScrollbar = function () { // thx walsh | ||
var scrollDiv = document.createElement('div') | ||
scrollDiv.className = 'modal-scrollbar-measure' | ||
this.$body.append(scrollDiv) | ||
var scrollbarWidth = scrollDiv.offsetWidth - scrollDiv.clientWidth | ||
this.$body[0].removeChild(scrollDiv) | ||
return scrollbarWidth | ||
} | ||
// click on link modal | ||
var parentModalLauncher = searchParent(e.target, MODAL_JS_CLASS); | ||
if ((hasClass(e.target, MODAL_JS_CLASS) === true || parentModalLauncher !== '') && eventName === 'click') { | ||
var body = doc.querySelector('body'); | ||
var modalLauncher = parentModalLauncher !== '' ? findById(parentModalLauncher) : e.target; | ||
var modalPrefixClass = modalLauncher.hasAttribute(MODAL_PREFIX_CLASS_ATTR) === true ? modalLauncher.getAttribute(MODAL_PREFIX_CLASS_ATTR) + '-' : ''; | ||
var modalText = modalLauncher.hasAttribute(MODAL_TEXT_ATTR) === true ? modalLauncher.getAttribute(MODAL_TEXT_ATTR) : ''; | ||
var modalContentId = modalLauncher.hasAttribute(MODAL_CONTENT_ID_ATTR) === true ? modalLauncher.getAttribute(MODAL_CONTENT_ID_ATTR) : ''; | ||
var modalDescribedById = modalLauncher.hasAttribute(MODAL_DESCRIBEDBY_ID_ATTR) === true ? modalLauncher.getAttribute(MODAL_DESCRIBEDBY_ID_ATTR) : ''; | ||
var modalTitle = modalLauncher.hasAttribute(MODAL_TITLE_ATTR) === true ? modalLauncher.getAttribute(MODAL_TITLE_ATTR) : ''; | ||
var modalCloseText = modalLauncher.hasAttribute(MODAL_CLOSE_TEXT_ATTR) === true ? modalLauncher.getAttribute(MODAL_CLOSE_TEXT_ATTR) : MODAL_OVERLAY_TXT; | ||
var modalCloseTitle = modalLauncher.hasAttribute(MODAL_CLOSE_TITLE_ATTR) === true ? modalLauncher.getAttribute(MODAL_CLOSE_TITLE_ATTR) : modalCloseText; | ||
var modalCloseImgPath = modalLauncher.hasAttribute(MODAL_CLOSE_IMG_ATTR) === true ? modalLauncher.getAttribute(MODAL_CLOSE_IMG_ATTR) : ''; | ||
var backgroundEnabled = modalLauncher.hasAttribute(MODAL_DATA_BACKGROUND_ATTR) === true ? modalLauncher.getAttribute(MODAL_DATA_BACKGROUND_ATTR) : ''; | ||
var modalGiveFocusToId = modalLauncher.hasAttribute(MODAL_FOCUS_TO_ATTR) === true ? modalLauncher.getAttribute(MODAL_FOCUS_TO_ATTR) : ''; | ||
var wrapperBody = findById(WRAPPER_PAGE_JS); | ||
// MODAL PLUGIN DEFINITION | ||
// ======================= | ||
// insert overlay | ||
body.insertAdjacentHTML('beforeEnd', createOverlay({ | ||
text: modalCloseTitle, | ||
backgroundEnabled: backgroundEnabled, | ||
prefixClass: modalPrefixClass | ||
})); | ||
function Plugin(option, _relatedTarget) { | ||
return this.each(function () { | ||
var $this = $(this) | ||
var data = $this.data('bs.modal') | ||
var options = $.extend({}, Modal.DEFAULTS, $this.data(), typeof option == 'object' && option) | ||
// insert modal | ||
body.insertAdjacentHTML('beforeEnd', createModal({ | ||
modalText: modalText, | ||
modalPrefixClass: modalPrefixClass, | ||
backgroundEnabled: modalContentId, | ||
modalTitle: modalTitle, | ||
modalCloseText: modalCloseText, | ||
modalCloseTitle: modalCloseTitle, | ||
modalCloseImgPath: modalCloseImgPath, | ||
modalContentId: modalContentId, | ||
modalDescribedById: modalDescribedById, | ||
modalFocusBackId: modalLauncher.getAttribute('id') | ||
})); | ||
if (!data) $this.data('bs.modal', (data = new Modal(this, options))) | ||
if (typeof option == 'string') data[option](_relatedTarget) | ||
else if (options.show) data.show(_relatedTarget) | ||
}) | ||
} | ||
// hide page | ||
wrapperBody.setAttribute(ATTR_HIDDEN, 'true'); | ||
var old = $.fn.modal | ||
// add class noscroll to body | ||
addClass(body, NO_SCROLL_CLASS); | ||
$.fn.modal = Plugin | ||
$.fn.modal.Constructor = Modal | ||
// give focus to close button or specified element | ||
var closeButton = findById(MODAL_BUTTON_JS_ID); | ||
if (modalGiveFocusToId !== '') { | ||
var focusTo = findById(modalGiveFocusToId); | ||
if (focusTo) { | ||
focusTo.focus(); | ||
} else { | ||
closeButton.focus(); | ||
} | ||
} else { | ||
closeButton.focus(); | ||
} | ||
e.preventDefault(); | ||
} | ||
// MODAL NO CONFLICT | ||
// ================= | ||
// click on close button or on overlay not blocked | ||
var parentButton = searchParent(e.target, MODAL_BUTTON_JS_CLASS); | ||
if ((e.target.getAttribute('id') === MODAL_BUTTON_JS_ID || parentButton !== '' || e.target.getAttribute('id') === MODAL_OVERLAY_ID || hasClass(e.target, MODAL_BUTTON_JS_CLASS) === true) && eventName === 'click') { | ||
var body = doc.querySelector('body'); | ||
var wrapperBody = findById(WRAPPER_PAGE_JS); | ||
var modal = findById(MODAL_JS_ID); | ||
var modalContent = findById(MODAL_CONTENT_JS_ID) ? findById(MODAL_CONTENT_JS_ID).innerHTML : ''; | ||
var overlay = findById(MODAL_OVERLAY_ID); | ||
var modalButtonClose = findById(MODAL_BUTTON_JS_ID); | ||
var modalFocusBackId = modalButtonClose.getAttribute(MODAL_BUTTON_FOCUS_BACK_ID); | ||
var contentBackId = modalButtonClose.getAttribute(MODAL_BUTTON_CONTENT_BACK_ID); | ||
var backgroundEnabled = overlay.getAttribute(MODAL_OVERLAY_BG_ENABLED_ATTR); | ||
$.fn.modal.noConflict = function () { | ||
$.fn.modal = old | ||
return this | ||
} | ||
if (!(e.target.getAttribute('id') === MODAL_OVERLAY_ID && backgroundEnabled === 'disabled')) { | ||
closeModal({ | ||
modal: modal, | ||
modalContent: modalContent, | ||
overlay: overlay, | ||
modalFocusBackId: modalFocusBackId, | ||
contentBackId: contentBackId, | ||
backgroundEnabled: backgroundEnabled, | ||
fromId: e.target.getAttribute('id') | ||
}); | ||
// MODAL DATA-API | ||
// ============== | ||
// show back page | ||
wrapperBody.removeAttribute(ATTR_HIDDEN); | ||
$(document).on('click.bs.modal.data-api', '[data-toggle="modal"]', function (e) { | ||
var $this = $(this) | ||
var href = $this.attr('href') | ||
var $target = $($this.attr('data-target') || (href && href.replace(/.*(?=#[^\s]+$)/, ''))) // strip for ie7 | ||
var option = $target.data('bs.modal') ? 'toggle' : $.extend({ remote: !/#/.test(href) && href }, $target.data(), $this.data()) | ||
// remove class noscroll to body | ||
removeClass(body, NO_SCROLL_CLASS); | ||
} | ||
} | ||
if ($this.is('a')) e.preventDefault() | ||
// strike a key when modal opened | ||
if (findById(MODAL_JS_ID) && eventName === 'keydown') { | ||
var body = doc.querySelector('body'); | ||
var wrapperBody = findById(WRAPPER_PAGE_JS); | ||
var modal = findById(MODAL_JS_ID); | ||
var modalContent = findById(MODAL_CONTENT_JS_ID) ? findById(MODAL_CONTENT_JS_ID).innerHTML : ''; | ||
var overlay = findById(MODAL_OVERLAY_ID); | ||
var modalButtonClose = findById(MODAL_BUTTON_JS_ID); | ||
var modalFocusBackId = modalButtonClose.getAttribute(MODAL_BUTTON_FOCUS_BACK_ID); | ||
var contentBackId = modalButtonClose.getAttribute(MODAL_BUTTON_CONTENT_BACK_ID); | ||
var $listFocusables = [].slice.call(modal.querySelectorAll(FOCUSABLE_ELEMENTS_STRING)); | ||
$target.one('show.bs.modal', function (showEvent) { | ||
if (showEvent.isDefaultPrevented()) return // only register focus restorer if modal will actually get shown | ||
$target.one('hidden.bs.modal', function () { | ||
$this.is(':visible') && $this.trigger('focus') | ||
}) | ||
}) | ||
Plugin.call($target, option, this) | ||
}) | ||
// esc | ||
if (e.keyCode === 27) { | ||
// | ||
// jQuery Focusable and tabable Selector from jQueryUi | ||
// --------------------------------------------------------------------------- | ||
closeModal({ | ||
modal: modal, | ||
modalContent: modalContent, | ||
overlay: overlay, | ||
modalFocusBackId: modalFocusBackId, | ||
contentBackId: contentBackId | ||
}); | ||
/*! | ||
// selectors Courtesy: https://github.com/jquery/jquery-ui/blob/master/ui/focusable.js and tabbable.js | ||
// show back page | ||
wrapperBody.removeAttribute(ATTR_HIDDEN); | ||
Copyright jQuery Foundation and other contributors, https://jquery.org/ | ||
This software consists of voluntary contributions made by many | ||
individuals. For exact contribution history, see the revision history | ||
available at https://github.com/jquery/jquery-ui | ||
The following license applies to all parts of this software except as | ||
documented below: | ||
==== | ||
Permission is hereby granted, free of charge, to any person obtaining | ||
a copy of this software and associated documentation files (the | ||
"Software"), to deal in the Software without restriction, including | ||
without limitation the rights to use, copy, modify, merge, publish, | ||
distribute, sublicense, and/or sell copies of the Software, and to | ||
permit persons to whom the Software is furnished to do so, subject to | ||
the following conditions: | ||
The above copyright notice and this permission notice shall be | ||
included in all copies or substantial portions of the Software. | ||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | ||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | ||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | ||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE | ||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION | ||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION | ||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | ||
==== | ||
Copyright and related rights for sample code are waived via CC0. Sample | ||
code is defined as all source code contained within the demos directory. | ||
CC0: http://creativecommons.org/publicdomain/zero/1.0/ | ||
==== | ||
*/ | ||
// remove class noscroll to body | ||
removeClass(body, NO_SCROLL_CLASS); | ||
} | ||
var focusable = function(element, isTabIndexNotNaN) { | ||
var map, mapName, img, | ||
nodeName = element.nodeName.toLowerCase(); | ||
if ("area" === nodeName) { | ||
map = element.parentNode; | ||
mapName = map.name; | ||
if (!element.href || !mapName || map.nodeName.toLowerCase() !== "map") { | ||
return false; | ||
} | ||
img = $("img[usemap='#" + mapName + "']")[0]; | ||
return !!img && visible(img); | ||
} | ||
return (/input|select|textarea|button|object/.test(nodeName) ? | ||
!element.disabled : | ||
"a" === nodeName ? | ||
element.href || isTabIndexNotNaN : isTabIndexNotNaN) && visible(element); // the element and all of its ancestors must be visible | ||
} | ||
// tab or Maj Tab in modal => capture focus | ||
if (e.keyCode === 9 && $listFocusables.indexOf(e.target) >= 0) { | ||
var visible = function(element) { | ||
return $.expr.filters.visible(element) && | ||
!$(element).parents().addBack().filter(function() { | ||
return $.css(this, "visibility") === "hidden"; | ||
}).length; | ||
} | ||
// maj-tab on first element focusable => focus on last | ||
if (e.shiftKey) { | ||
if (e.target === $listFocusables[0]) { | ||
$listFocusables[$listFocusables.length - 1].focus(); | ||
e.preventDefault(); | ||
} | ||
} else { | ||
// tab on last element focusable => focus on first | ||
if (e.target === $listFocusables[$listFocusables.length - 1]) { | ||
$listFocusables[0].focus(); | ||
e.preventDefault(); | ||
} | ||
} | ||
} | ||
$.extend( $.expr[ ":" ], { | ||
data: $.expr.createPseudo ? | ||
$.expr.createPseudo(function( dataName ) { | ||
return function( elem ) { | ||
return !!$.data( elem, dataName ); | ||
}; | ||
}) : | ||
// support: jQuery <1.8 | ||
function( elem, i, match ) { | ||
return !!$.data( elem, match[ 3 ] ); | ||
}, | ||
// tab outside modal => put it in focus | ||
if (e.keyCode === 9 && $listFocusables.indexOf(e.target) === -1) { | ||
e.preventDefault(); | ||
$listFocusables[0].focus(); | ||
} | ||
} | ||
}, true); | ||
}); | ||
} | ||
}; | ||
focusable: function( element ) { | ||
return focusable( element, !isNaN( $.attr( element, "tabindex" ) ) ); | ||
}, | ||
var onLoad = function onLoad() { | ||
attach(); | ||
document.removeEventListener('DOMContentLoaded', onLoad); | ||
}; | ||
tabbable: function( element ) { | ||
var tabIndex = $.attr( element, "tabindex" ), | ||
isTabIndexNaN = isNaN( tabIndex ); | ||
return ( isTabIndexNaN || tabIndex >= 0 ) && focusable( element, !isTabIndexNaN ); | ||
} | ||
}); | ||
document.addEventListener('DOMContentLoaded', onLoad); | ||
window.van11yAccessibleModalWindowAria = attach; | ||
})(document); | ||
// Modal Extension | ||
// =============================== | ||
// base : https://github.com/paypal/bootstrap-accessibility-plugin/blob/master/src/js/modal.js | ||
}; | ||
/*! | ||
https://github.com/paypal/bootstrap-accessibility-plugin/blob/master/LICENSE.md | ||
Copyright (c) 2014, PayPal All rights reserved. | ||
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: | ||
Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. | ||
Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. | ||
Neither the name of the PayPal nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. | ||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
*/ | ||
var modalFocus = $.fn.modal.Constructor.prototype.enforceFocus, | ||
modalHide = $.fn.modal.Constructor.prototype.hide, | ||
modalShow = $.fn.modal.Constructor.prototype.show; | ||
$('.modal-dialog').attr({'role': 'document'}); | ||
$.fn.modal.Constructor.prototype.hide = function () { | ||
modalHide.apply(this, arguments); | ||
$(document).off('keydown.bs.modal'); | ||
}; | ||
$.fn.modal.Constructor.prototype.show = function () { | ||
modalShow.apply(this, arguments); | ||
var $tabbablesAndFocusables = this.$element.find(':tabbable').filter(':focusable'); | ||
if($tabbablesAndFocusables.length > 0) { | ||
$tabbablesAndFocusables[0].focus(); | ||
} | ||
}; | ||
$.fn.modal.Constructor.prototype.enforceFocus = function () { | ||
var focusElements = this.$element.find(':tabbable'), | ||
lastElement = focusElements[focusElements.length - 1], | ||
firstElement = focusElements[0]; | ||
$(document).on('keydown.bs.modal', $.proxy(function (event) { | ||
var tabEvent = event.keyCode === 9, | ||
backwardTabEvent = event.shiftKey && tabEvent, | ||
forwardTabEvent = !event.shiftKey && tabEvent, | ||
targetFirstElement = $(event.target).is(firstElement), | ||
targetLastElement = $(event.target).is(lastElement); | ||
if (targetFirstElement && backwardTabEvent) { | ||
lastElement.focus(); | ||
event.preventDefault(); | ||
} else if (targetLastElement && forwardTabEvent) { | ||
firstElement.focus(); | ||
event.preventDefault(); | ||
} | ||
}, this)); | ||
modalFocus.apply(this, arguments) | ||
}; | ||
}(jQuery); | ||
Scampi.modal(); |
@@ -6,4 +6,38 @@ # Modal | ||
Le module modal de Scampi est largement basé sur celui de [Bootstrap](http://getbootstrap.com/javascript/#modals) auquel lui a été ajouté le [module bootstrap-accessibility-plugin](https://github.com/paypal/bootstrap-accessibility-plugin) de paypal ainsi que quelques autres modifications pour une pleine conformité RGAA. | ||
Le module modal de Scampi est largement basé sur celui de [Van11y](https://van11y.net/accessible-modal/) auquel lui a été ajouté quelques autres modifications pour une pleine conformité RGAA. | ||
Utilisation | ||
------------------------------------------------------------------------------ | ||
Le bouton appelant la modale porte la classe `js-modal` et les attributs `data-modal-close-text="×" data-modal-close-title="Fermer"` et `data-modal-content-id="exempleModal"` où `exempleModal` est l’id de la fenêtre modale appelée. | ||
Note : le cas sans js n'a pas encore été traité, cela fera l'objet d'une prochaine mise à jour. | ||
### Configuration | ||
Les variables proposées dans ce module sont : | ||
- `$zindex-modal` : z-index de la modale, sa valeur par défaut est `1050` | ||
- `$zindex-modal-background` : z-index du fond de la modale, sa valeur par défaut est `1040` | ||
- `$modal-inner-padding` : padding de la modale, sa valeur par défaut est `1em` | ||
- `$modal-title-padding` : padding du titre de la modale, sa valeur par défaut est `1rem` | ||
- `$modal-title-line-height` : hauteur de ligne du titre, sa valeur par défaut est `$line-height-sm` | ||
- `$modal-content-bg` : couleur de fond de la modale, sa valeur par défaut est `#fff` | ||
- `$modal-content-border-color` : couleur de la bordure de la modale, sa valeur par défaut est `rgba(0,0,0,.2)` | ||
- `$modal-content-fallback-border-color` : couleur de la bordure de la modale pour **IE8**, sa valeur par défaut est `#999` | ||
//** Modal backdrop background color | ||
- `$modal-backdrop-bg` : couleur de l'arrière plan de la modale, sa valeur par défaut est `#000` | ||
- `$modal-backdrop-opacity` : opacité de l'arrière plan de la modale, sa valeur par défaut est `.5` | ||
- `$modal-header-border-color` : bordure du header de la modale, sa valeur par défaut est `#e5e5e5` | ||
- `$modal-footer-border-color` : bordure du footer de la modale, sa valeur par défaut est `$modal-header-border-color` | ||
- `$modal-lg` : taille de la modale en affichage desktop, sa valeur par défaut est `56em` | ||
- `$modal-md` : taille de la modale en affichage tablette, sa valeur par défaut est `37em` | ||
- `$modal-sm` : taille de la modale en affichage mobile, sa valeur par défaut est `19em` | ||
- `$modal-close-font-size` : taille du texte du bouton fermer, sa valeur par défaut est `$font-size-base * 1.5` | ||
- `$modal-close-font-weight` : font-weight du bouton fermer, sa valeur par défaut est `bold` | ||
- `$modal-close-color` : couleur du bouton fermer, sa valeur par défaut est `#000` | ||
- `$modal-close-padding` : padding du bouton fermer, sa valeur par défaut est `0.1em 0.3em` | ||
### Accessibilité | ||
@@ -17,10 +51,2 @@ | ||
Utilisation | ||
------------------------------------------------------------------------------ | ||
Le bouton appelant la modale porte les attributs `data-toggle="modal"` et `data-target="#exempleModal"` où `#exempleModal` est l’id de la fenêtre modale appelée. | ||
Note : le cas sans js n'a pas encore été traité, cela fera l'objet d'une prochaine mise à jour. | ||
### Script associé | ||
@@ -30,5 +56,3 @@ | ||
Note : copier le script présent dans le module à l'endroit où sont rangés les autres scripts. | ||
Exemple d’utilisation | ||
@@ -38,27 +62,18 @@ ------------------------------------------------------------------------------ | ||
```html | ||
<button class="btn btn-primary js-modal" data-modal-content-id="exempleModal" data-modal-close-text="×" data-modal-close-title="Fermer">Hop la modale !</button> | ||
<button class="btn btn-primary" data-target="#exempleModal" data-toggle="modal">Hop la modale !</button> | ||
<div class="modal fade" id="exempleModal" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel" aria-hidden="true"> | ||
<div class="modal-dialog" role="document"> | ||
<div class="modal-content"> | ||
<div class="modal-header"> | ||
<button class="btn btn-secondary btn-close" type="button" data-dismiss="modal" title="Fermer" aria-describedby="exampleModalLabel"> | ||
<span aria-hidden="true">×</span> | ||
<span class="sr-only">Fermer</span> | ||
</button> | ||
<h5 class="modal-title" id="exampleModalLabel">Titre de la modale</h5> | ||
</div> | ||
<div class="modal-body"> | ||
<p>Contenu de la modale</p> | ||
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. At consequatur ratione similique ipsa rem ab dicta quaerat voluptate rerum asperiores temporibus illum qui explicabo molestias, quas harum. Dolor, magni, labore.</p> | ||
</div> | ||
<div class="modal-footer"> | ||
<button type="button" class="btn btn-secondary" data-dismiss="modal">Annuler</button> | ||
<button type="button" class="btn btn-primary">Continuer</button> | ||
</div> | ||
</div> | ||
<div class="modal-template" id="exempleModal"> | ||
<div class="modal-header"> | ||
<h5 id="modal-title" class="modal-title" id="exampleModalLabel">Titre de la modale</h5> | ||
</div> | ||
<div class="modal-body"> | ||
<p>Contenu de la modale</p> | ||
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. At consequatur ratione similique ipsa rem ab dicta quaerat voluptate rerum asperiores temporibus illum qui explicabo molestias, quas harum. Dolor, magni, labore.</p> | ||
</div> | ||
<div class="modal-footer"> | ||
<button type="button" class="btn btn-secondary js-modal-close">Annuler</button> | ||
<button type="button" class="btn btn-primary">Continuer</button> | ||
</div> | ||
</div> | ||
``` |
# Pagination | ||
Présentation | ||
------------------------------------------------------------------------------ | ||
----------------------- | ||
@@ -10,5 +10,24 @@ Module de pagination : division de l’affichage d’une page de résultats pour une recherche. | ||
Accessibilité | ||
------------------------------------------------------------------------------ | ||
Utilisation | ||
------------------------- | ||
### Configuration | ||
Les variables proposées dans ce module sont : | ||
- `$zindex-modal` : z-index de la modale, sa valeur par défaut est `1050` | ||
- `$zindex-modal-background` : z-index du fond de la modale, sa valeur par défaut est `1040` | ||
- `$page-link-color` : couleur des liens, sa valeur par défaut est `$body-color` | ||
- `$page-link-bg` : couleur de fond des liens, sa valeur par défaut est `transparent` | ||
- `$page-link-border` : couleur de la bordure des liens, sa valeur par défaut est `$primary-color` | ||
- `$page-link-color-focus` : couleur des liens au focus, sa valeur par défaut est `#fff` | ||
- `$page-link-bg-focus` : couleur de fond des liens au focus, sa valeur par défaut est `$primary-color` | ||
- `$page-link-active-color` : couleur des liens actifs, sa valeur par défaut est `$page-link-color-focus` | ||
- `$page-link-active-bg` : couleur de fond des liens actifs, sa valeur par défaut est `$page-link-bg-focus` | ||
- `$page-item-prev-icon` : icône précédente, sa valeur par défaut est `"\2039"` | ||
- `$page-item-next-icon` : icône suivante, sa valeur par défaut est `"\203A"` | ||
### Accessibilité | ||
Placer des attributs : | ||
@@ -23,9 +42,7 @@ | ||
Responsive | ||
------------------------------------------------------------------------------ | ||
### Responsive | ||
Sur petit écran, les liens conduisant vers la page précédente ou la page suivante n’affichent que le chevron, sans l’intitulé (qui est restitué par les synthèses vocales grâce au mixin sr-only). | ||
Complément | ||
------------------------------------------------------------------------------ | ||
### À noter | ||
@@ -32,0 +49,0 @@ Lors de l’intégration réelle, le lien "page précédente" doit être désactivé si l’on est sur la première page et le lien "page suivante" doit être désactivé si l’on est sur la dernière page. |
@@ -88,2 +88,3 @@ # Utiliser un module | ||
| [rwd-utils](https://pidila.gitlab.io/scampi/documentation/rwd-utils.html) | Fonctions, mixins et styles utilitaires pour le responsive. | | ||
| [select-a11y](https://pidila.gitlab.io/scampi/documentation/select-a11y.html) | Module qui transforme un élément select (multiple ou non) en liste de suggestions avec champ de recherche à l'intérieur de cette liste. | | ||
| [site-banner-simple](https://pidila.gitlab.io/scampi/documentation/site-banner-simple.html) | Version basique d'un entête de site avec logo et nom du site (peut servir de base pour un placement de marianne). | | ||
@@ -90,0 +91,0 @@ | [skip-link](https://pidila.gitlab.io/scampi/documentation/skip-link.html) | Liens d'évitement, aussi appelés liens d'accès rapides. | |
@@ -17,4 +17,4 @@ # RWD-utils | ||
Exemple | ||
------- | ||
Exemple d'utilisation | ||
--------------------- | ||
@@ -21,0 +21,0 @@ 1. Créer un fichier _rwd-utils.scss dans le répertoire projet/modules avec le contenu suivant : |
@@ -8,6 +8,15 @@ # Site-banner-simple | ||
Responsive | ||
Utilisation | ||
------------- | ||
### Configuration | ||
Les variables proposées dans ce module sont : | ||
- `$site-title-size-small` : petite taille du titre, sa valeur par défaut est `1.5em` | ||
- `$site-title-size-large` : grande taille du titre, sa valeur par défaut est `2em` | ||
- `$banner-hover-background` : couleur de fond au survol, sa valeur par défaut est `$gray-9` | ||
### Responsive | ||
Sur mobile, la baseline est sous le logo et le titre ; sur les plus grandes résolutions, la baseline se positionne sous le titre et le logo est calé à gauche. | ||
@@ -14,0 +23,0 @@ |
@@ -5,10 +5,46 @@ /*! | ||
*/ | ||
var Scampi = Scampi || {}; | ||
$(document).ready(function(){ | ||
// affichage et masquage des liens d'évitement | ||
$('.skip-link a').on('focus', function () { | ||
$(this).parents('.container').addClass('skip-link-focus'); | ||
}).on('blur', function () { | ||
$(this).parents('.container').removeClass('skip-link-focus'); | ||
}); | ||
}); | ||
if(document.documentElement.classList.contains('no-js')){ | ||
document.documentElement.classList.remove('no-js'); | ||
document.documentElement.classList.add('js'); | ||
} | ||
Scampi.skipLinks = function skipLinks(){ | ||
var matches = Element.prototype.matches || Element.prototype.msMatchesSelector || Element.prototype.webkitMatchesSelector; | ||
var closest = Element.prototype.closest; | ||
if (!closest) { | ||
closest = function(s) { | ||
var el = this; | ||
do { | ||
if (matches.call(el, s)) return el; | ||
el = el.parentElement || el.parentNode; | ||
} while (el !== null && el.nodeType === 1); | ||
return null; | ||
}; | ||
} | ||
function toggleSkipLinks(evt){ | ||
var link = closest.call(evt.target, ".skip-link a"); | ||
if(!link) { | ||
return; | ||
} | ||
var container = closest.call(link, '.container'); | ||
if(event.type === 'focus'){ | ||
container.classList.add('skip-link-focus'); | ||
} | ||
else { | ||
container.classList.remove('skip-link-focus'); | ||
} | ||
} | ||
document.body.addEventListener("focus", toggleSkipLinks, true); | ||
document.body.addEventListener("blur", toggleSkipLinks, true); | ||
} | ||
Scampi.skipLinks(); |
@@ -20,2 +20,10 @@ # Skip-link | ||
### Configuration | ||
Les variables proposées dans ce module sont : | ||
- `$enable-skip-link-js` : activation du module, sa valeur par défaut est `true` | ||
- `$skip-link-bg` : couleur de fond, sa valeur par défaut est `black` | ||
- `$skip-link-color` : couleur des liens, sa valeur par défaut est `white` | ||
### Script associé | ||
@@ -29,4 +37,4 @@ | ||
Exemple | ||
------- | ||
Exemple d’utilisation | ||
------------- | ||
@@ -36,3 +44,3 @@ ````html | ||
<div id="top"> | ||
<ul> | ||
<ul class="container"> | ||
<li><a href="#main">Aller au contenu</a></li> | ||
@@ -39,0 +47,0 @@ <li><a href="#nav-main">Aller à la navigation</a></li> |
@@ -12,6 +12,5 @@ # SVG-icons | ||
### Accessibilité | ||
Utilisation | ||
--------------------------------------------------------------------- | ||
Les icônes ne jouent qu'un rôle décoratif. Pour ne pas « polluer » la lecture par les aides techniques on ajoute un attribut ```aria-hidden="true"``` et pour contourner un [bug d'Internet Explorer](http://simplyaccessible.com/article/7-solutions-svgs/#acc-heading-4) qui déclenche la prise de focus sur les svg même lorsqu'ils ne sont pas des liens on ajoute également l'attribut ```focusable="false"```. | ||
### Styler les svg | ||
@@ -23,2 +22,6 @@ | ||
### Accessibilité | ||
Les icônes ne jouent qu'un rôle décoratif. Pour ne pas « polluer » la lecture par les aides techniques on ajoute un attribut ```aria-hidden="true"``` et pour contourner un [bug d'Internet Explorer](http://simplyaccessible.com/article/7-solutions-svgs/#acc-heading-4) qui déclenche la prise de focus sur les svg même lorsqu'ils ne sont pas des liens on ajoute également l'attribut ```focusable="false"```. | ||
### Script associé | ||
@@ -25,0 +28,0 @@ |
@@ -19,7 +19,23 @@ # Tables | ||
### Configuration | ||
Les variables proposées dans ce module sont : | ||
Exemple | ||
------- | ||
- `$table-cell-padding` : padding des cellules, sa valeur par défaut est `.75em` | ||
- `$table-sm-cell-padding` : padding des cellules en affichage mobile, sa valeur par défaut est `.25em` | ||
- `$table-bg` : couleur de fond, sa valeur par défaut est `transparent` | ||
- `$table-bg-accent` : couleur de fond, sa valeur par défaut est `$gray-9` | ||
- `$table-bg-hover` : couleur de fond au survol, sa valeur par défaut est `$gray-10` | ||
- `$table-bg-active` : couleur de fond , sa valeur par défaut est `$table-bg-hover` | ||
- `$table-border-width` : bordure, sa valeur par défaut est `$border-width` | ||
- `$table-border-color` : couleur de la bordure, sa valeur par défaut est `$gray-9` | ||
- `$table-inverse-bg-color` : couleur de fond "inverse", sa valeur par défaut est `$gray-1` | ||
- `$table-inverse-text-color` : couleur de texte "inverse", sa valeur par défaut est `#fff` | ||
- `$table-inverse-border-color` : couleur de la bordure "inverse", sa valeur par défaut est `$gray-9` | ||
Exemple d’utilisation | ||
--------------------------------------------------------------------- | ||
```` html | ||
@@ -26,0 +42,0 @@ <table class="table"> |
@@ -6,29 +6,30 @@ /*! | ||
*/ | ||
var Scampi = Scampi || {}; | ||
function textareaCounter() { | ||
var $textAreaField = $("textarea[maxlength]"); | ||
Scampi.textareaCounter = function textareaCounter() { | ||
var textAreaFields = document.querySelectorAll("textarea[maxlength]"); | ||
var stepPolite = 100; | ||
var stepAssertive = 20; | ||
$textAreaField.each(function (index, textarea) { | ||
var maxLength = $(textarea).attr("maxlength"); | ||
var messageLength = $(textarea).val().length; | ||
Array.prototype.forEach.call(textAreaFields, function (textarea) { | ||
var maxLength = textarea.getAttribute("maxlength"); | ||
var messageLength = textarea.value.length; | ||
var activeValue = countRest(maxLength, messageLength); | ||
var idTextarea = $(textarea).attr("id"); | ||
var $textarea = undefined; | ||
var $paragraph = undefined; | ||
var idTextarea = textarea.id; | ||
$(textarea).attr('aria-describedby', idTextarea + '-counter'); | ||
textarea.setAttribute("aria-describedby", idTextarea + "-counter"); | ||
$(textarea).after('<p class="textarea-counter" id="' + idTextarea + '-counter"><span class="textarea-counter-nb">' + activeValue + '</span> caractères restants</p>'); | ||
textarea.insertAdjacentHTML("afterend", "<p class='textarea-counter' id='" + idTextarea + "-counter'><span class='textarea-counter-nb'>" + activeValue + "</span> caractères restants</p>"); | ||
$(textarea).on("input", function (e) { | ||
$textarea = $(e.currentTarget); | ||
$paragraph = $textarea.next('p.textarea-counter'); | ||
updateValue($textarea, $paragraph); | ||
}); | ||
textarea.addEventListener("input", handleInput); | ||
textarea.addEventListener("keypress", handleInput); | ||
}); | ||
function handleInput(evt) { | ||
textarea = evt.target; | ||
paragraph = textarea.nextElementSibling; | ||
updateValue(textarea, paragraph); | ||
} | ||
function countRest(maxlength, messageLength) { | ||
@@ -46,3 +47,3 @@ return maxlength - messageLength; | ||
function updateAria(maxLengthValue, messageLength, $paragraph) { | ||
function updateAria(maxLengthValue, messageLength, paragraph) { | ||
politeFlag = countStepPolite(maxLengthValue); | ||
@@ -52,31 +53,25 @@ assertiveFlag = countStepAssertive(maxLengthValue); | ||
if (messageLength < politeFlag) { | ||
$paragraph | ||
.removeAttr('aria-live') | ||
.removeAttr('aria-atomic'); | ||
paragraph.removeAttribute("aria-live"); | ||
paragraph.removeAttribute("aria-atomic"); | ||
} | ||
else if (messageLength >= politeFlag && messageLength < assertiveFlag) { | ||
$paragraph.attr({ | ||
'aria-live': 'polite', | ||
'aria-atomic': 'true' | ||
}); | ||
paragraph.setAttribute("aria-live", "polite"); | ||
paragraph.setAttribute("aria-atomic", "true"); | ||
} | ||
else if (messageLength >= assertiveFlag) { | ||
$paragraph.attr({ | ||
'aria-live': 'assertive', | ||
'aria-atomic': 'true' | ||
}); | ||
paragraph.setAttribute("aria-live", "assertive"); | ||
paragraph.setAttribute("aria-atomic", "true"); | ||
} | ||
} | ||
function updateValue($textarea, $paragraph) { | ||
var maxLength = $textarea.attr("maxlength"); | ||
var messageLength = $textarea.val().length; | ||
$paragraph.find('.textarea-counter-nb').text(countRest(maxLength, messageLength)); | ||
updateAria(maxLength, messageLength, $paragraph); | ||
function updateValue(textarea, paragraph) { | ||
var maxLength = textarea.getAttribute("maxlength"); | ||
var messageLength = textarea.value.length; | ||
var counter = paragraph.querySelector(".textarea-counter-nb") | ||
counter.innerText = countRest(maxLength, messageLength); | ||
updateAria(maxLength, messageLength, paragraph); | ||
} | ||
} | ||
$(document).ready(function () { | ||
textareaCounter(); | ||
}); | ||
Scampi.textareaCounter(); |
@@ -5,3 +5,3 @@ # Textarea counter | ||
Présentation | ||
------------------------------------------------------------------------------ | ||
------------------------------------------- | ||
@@ -14,3 +14,3 @@ Ce script permet d’ajouter le décompte de caractères restant sur un élément de formulaire de type `textarea` lorsque que l’attribut `maxlength` est défini. | ||
Ils sont fixés pour : | ||
Ils sont fixés pour : | ||
- `aria-live="polite"` lorsqu’il reste 100 caractères ou moins | ||
@@ -21,3 +21,3 @@ - `aria-live="assertive"` lorsqu’il reste 20 caractères ou moins | ||
Utilisation | ||
------------------------------------------------------------------------------ | ||
--------------------------------------------------- | ||
@@ -28,6 +28,12 @@ Inclure le script et le style à votre projet. | ||
### Configuration | ||
Exemple | ||
------------------------------------------------------------------------------ | ||
La variable proposée dans ce module est : | ||
- `$textarea-counter-nb-color` : couleur du compteur, sa valeur par défaut est `$primary-color` | ||
Exemple d’utilisation | ||
--------------------------------------------------------------------- | ||
### Code écrit | ||
@@ -48,5 +54,5 @@ | ||
<textarea class="form-control" id="textarea-1" cols="30" rows="4" maxlength="240" ariadescribedby="textarea-1-counter"></textarea> | ||
<p class="textarea-counter" id="textarea-1-counter"><span class="textarea-counter-nb">500</span> caractères restants</p> | ||
<p class="textarea-counter" id="textarea-1-counter"><span class="textarea-counter-nb">240</span> caractères restants</p> | ||
</div> | ||
``` | ||
@@ -10,13 +10,25 @@ /* sg-comments.js | ||
*/ | ||
$(document).ready(function(){ | ||
var Scampi = Scampi || {}; | ||
$('#sg-toggle-comments').click(function(e) { | ||
e.preventDefault(); | ||
$('.sg-comment').toggleClass('is-visible'); | ||
$(this).toggleClass('is-closed'); | ||
Scampi.uComments = function uComments(){ | ||
var toggle = document.getElementById('sg-toggle-comments'); | ||
var comments = document.querySelectorAll('.sg-comment'); | ||
var isExpanded=$(this).attr('aria-expanded'); | ||
isExpanded==='true' ? $(this).attr('aria-expanded','false') : $(this).attr('aria-expanded','true'); | ||
if(!toggle){ | ||
return; | ||
} | ||
function toggleVisibility(evt){ | ||
Array.prototype.forEach.call(comments, function(comment){ | ||
comment.classList.toggle('is-visible'); | ||
toggle.classList.toggle('is-closed'); | ||
var isExpanded = toggle.getAttribute('aria-expanded') === 'true'; | ||
toggle.setAttribute('aria-expanded', isExpanded ? 'false' : 'true'); | ||
}); | ||
}); | ||
} | ||
toggle.addEventListener('click', toggleVisibility); | ||
} | ||
Scampi.uComments(); |
@@ -21,4 +21,3 @@ # U-comments | ||
Accessibilité | ||
------------- | ||
### Accessibilité | ||
@@ -33,4 +32,4 @@ Un attribut aria-expanded (true ou false) est ajouté sur le bouton déclenchant l'affichage ou le masquage des commentaires. | ||
Exemple | ||
------- | ||
Exemple d’utilisation | ||
--------------------------------------------------------------------- | ||
@@ -37,0 +36,0 @@ ````html |
@@ -12,2 +12,5 @@ # U-debug | ||
Utilisation | ||
--------------------------------------------------------------------- | ||
### Debug rwd | ||
@@ -14,0 +17,0 @@ |
/******************* | ||
color swatch | ||
********************/ | ||
$(document).ready(function(){ | ||
//convert rgba color to hex color | ||
$.cssHooks.backgroundColor = { | ||
get: function(elem) { | ||
if (elem.currentStyle) | ||
var bg = elem.currentStyle["background-color"]; | ||
else if (window.getComputedStyle) | ||
var bg = document.defaultView.getComputedStyle(elem, | ||
null).getPropertyValue("background-color"); | ||
if (bg.search("rgb") == -1) | ||
return bg; | ||
else { | ||
bg = bg.match(/^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/); | ||
function hex(x) { | ||
return ("0" + parseInt(x).toString(16)).slice(-2); | ||
} | ||
return "#" + hex(bg[1]) + hex(bg[2]) + hex(bg[3]); | ||
} | ||
} | ||
var Scampi = Scampi || {}; | ||
Scampi.uPalette = function uPalette(){ | ||
var colors = [".sg-color-swatch", ".sg-color-swatch-lighten", ".sg-color-swatch-darken"]; | ||
var reBG = /^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/; | ||
function hex(x) { | ||
return ("0" + parseInt(x).toString(16)).slice(-2); | ||
} | ||
//set hex value for each color swatch | ||
$('.sg-color-swatch').each(function(){ | ||
var actual = $(this); | ||
$(this).append('<p class="sg-color-swatch-hex">'+actual.css("background-color")+'</p>'); | ||
}); | ||
$('.sg-color-swatch-lighten').each(function(){ | ||
var actual = $(this); | ||
$(actual).append(actual.css("background-color")); | ||
function getColor(el){ | ||
var bg = el.style.backgroundColor.length ? el.style.backgroundColor : window.getComputedStyle(el).getPropertyValue("background-color"); | ||
if(bg.search("rgb") === -1){ | ||
return bg; | ||
} | ||
bg = bg.match(reBG); | ||
return "#" + hex(bg[1]) + hex(bg[2]) + hex(bg[3]); | ||
} | ||
colors.forEach(function(type){ | ||
var colorSwatches = document.querySelectorAll(type); | ||
if(!colorSwatches){ | ||
return; | ||
} | ||
Array.prototype.forEach.call(colorSwatches, function(swatch){ | ||
if(swatch.lastElementChild){ | ||
swatch.lastElementChild.insertAdjacentHTML('afterend', '<p class="sg-color-swatch-hex">'+getColor(swatch)+'</p>') | ||
} | ||
else { | ||
swatch.innerText = getColor(swatch); | ||
} | ||
}); | ||
}); | ||
} | ||
$('.sg-color-swatch-darken').each(function(){ | ||
var actual = $(this); | ||
$(actual).append(actual.css("background-color")); | ||
}); | ||
}); | ||
Scampi.uPalette(); |
@@ -11,22 +11,25 @@ # U-palette | ||
### Variables prises en compte par défaut : | ||
* $body-color | ||
* $headings-color | ||
* $primary-color | ||
* $secondary-color | ||
* $info-color | ||
* $success-color | ||
* $warning-color | ||
* $danger-color | ||
* $gray-1 | ||
* $gray-2 | ||
* $gray-3 | ||
* $gray-4 | ||
* $gray-5 | ||
* $gray-6 | ||
* $gray-7 | ||
* $gray-8 | ||
* $gray-9 | ||
* $gray-10 | ||
### Configuration | ||
Les variables prises en compte par défaut sont : | ||
_ $body-color | ||
_ $headings-color | ||
_ $primary-color | ||
_ $secondary-color | ||
_ $info-color | ||
_ $success-color | ||
_ $warning-color | ||
_ $danger-color | ||
_ $gray-1 | ||
_ $gray-2 | ||
_ $gray-3 | ||
_ $gray-4 | ||
_ $gray-5 | ||
_ $gray-6 | ||
_ $gray-7 | ||
_ $gray-8 | ||
_ $gray-9 | ||
_ $gray-10 | ||
Pour ajouter un échantillon non prévu dans cette liste : | ||
@@ -39,4 +42,6 @@ | ||
Exemple d’utilisation | ||
--------------------------------------------------------------------- | ||
HTML à placer dans le styleguide | ||
-------------------------------- | ||
@@ -43,0 +48,0 @@ `NomVariable` est à remplacer par le nom de la variable. |
{ | ||
"name": "@pidila/scampi", | ||
"version": "0.6.1", | ||
"version": "0.7.0", | ||
"description": "Scampi est un ensemble de composants sass/js/html accessibles et responsive développés à la DILA (Direction de l’information légale et administrative). Il vise à être utilisé en interne et par nos prestataires ou partenaires.", | ||
@@ -5,0 +5,0 @@ "scripts": { |
@@ -28,3 +28,2 @@ # Scampi # | ||
* **modules/** : composants additionnels (scss, js) et leur documentation. | ||
* **scripts/** : scripts communs à tous les projets. | ||
* le fichier scampi.scss présente un "sommaire" des fichiers de la bibliothèque | ||
@@ -48,4 +47,2 @@ * le présent README | ||
Les modules comportant des scripts javascript s'appuient sur la bibliothèque jquery (non livrée avec Scampi). | ||
## Installation | ||
@@ -55,3 +52,3 @@ | ||
### En module npm | ||
### En module npm ou yarn | ||
@@ -67,5 +64,5 @@ Si votre projet comporte déjà des dépendances à des modules listés dans le fichier `package.json`. | ||
### En submodule | ||
### En submodule git | ||
Si vous utilisez Git pour vos développements et souhaitez mettre à jour Scampi au fil de ses développements, ajoutez Scampi à votre projet en déclarant un submodule au sein de votre répertoire de styles. Cela permet d’être informé des mises à jour de Scampi et de les récupérer si vous le souhaitez. | ||
Scampi peut également être utilisé en déclarant un submodule au sein de votre dépôt. Attention en ce cas à ajuster les chemins d'import des fichiers. | ||
@@ -96,4 +93,2 @@ ### En téléchargeant Scampi | ||
| Debugging Sass Maps | --- | --- | https://www.sitepoint.com/debugging-sass-maps/ | | ||
| jquery hide/show | v1.8.0 | MIT | http://a11y.nicolas-hoffmann.net/hide-show/ | | ||
| normalize.css | v3.0.3 | MIT License | github.com/necolas/normalize.css | | ||
| responsive-typography | --- | --- | https://github.com/liquidlight/responsive-typography | | ||
@@ -100,0 +95,0 @@ | Bootstrap | v4.0.0-alpha-4 | MIT License | https://github.com/twbs/bootstrap | |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
378824
154
1704
117
4