TP_JO2024/home/dist/component/tab/tab.module.js

397 lines
10 KiB
JavaScript
Raw Normal View History

2024-03-27 17:19:37 +01:00
/*! DSFR v1.11.2 | SPDX-License-Identifier: MIT | License-Filename: LICENSE.md | restricted use (see terms and conditions) */
const config = {
prefix: 'fr',
namespace: 'dsfr',
organisation: '@gouvfr',
version: '1.11.2'
};
const api = window[config.namespace];
/**
* TabButton correspond au bouton cliquable qui change le panel
* TabButton étend de DisclosureButton qui ajoute/enelve l'attribut aria-selected,
* Et change l'attributte tabindex a 0 si le boutton est actif (value=true), -1 s'il n'est pas actif (value=false)
*/
class TabButton extends api.core.DisclosureButton {
constructor () {
super(api.core.DisclosureType.SELECT);
}
static get instanceClassName () {
return 'TabButton';
}
handleClick (e) {
super.handleClick(e);
this.focus();
}
apply (value) {
super.apply(value);
if (this.isPrimary) {
this.setAttribute('tabindex', value ? '0' : '-1');
if (value) {
if (this.list) this.list.focalize(this);
}
}
}
get list () {
return this.element.getAscendantInstance('TabsList', 'TabsGroup');
}
}
const TabSelector = {
TAB: api.internals.ns.selector('tabs__tab'),
GROUP: api.internals.ns.selector('tabs'),
PANEL: api.internals.ns.selector('tabs__panel'),
LIST: api.internals.ns.selector('tabs__list'),
SHADOW: api.internals.ns.selector('tabs__shadow'),
SHADOW_LEFT: api.internals.ns.selector('tabs__shadow--left'),
SHADOW_RIGHT: api.internals.ns.selector('tabs__shadow--right'),
PANEL_START: api.internals.ns.selector('tabs__panel--direction-start'),
PANEL_END: api.internals.ns.selector('tabs__panel--direction-end')
};
const TabPanelDirection = {
START: 'direction-start',
END: 'direction-end',
NONE: 'none'
};
/**
* Tab coorespond au panel d'un élement Tabs (tab panel)
* Tab étend disclosure qui ajoute/enleve le modifier --selected,
* et ajoute/eleve l'attribut hidden, sur le panel
*/
class TabPanel extends api.core.Disclosure {
constructor () {
super(api.core.DisclosureType.SELECT, TabSelector.PANEL, TabButton, 'TabsGroup');
this._direction = TabPanelDirection.NONE;
this._isPreventingTransition = false;
}
static get instanceClassName () {
return 'TabPanel';
}
get direction () {
return this._direction;
}
set direction (value) {
if (value === this._direction) return;
switch (this._direction) {
case TabPanelDirection.START:
this.removeClass(TabSelector.PANEL_START);
break;
case TabPanelDirection.END:
this.removeClass(TabSelector.PANEL_END);
break;
case TabPanelDirection.NONE:
break;
default:
return;
}
this._direction = value;
switch (this._direction) {
case TabPanelDirection.START:
this.addClass(TabSelector.PANEL_START);
break;
case TabPanelDirection.END:
this.addClass(TabSelector.PANEL_END);
break;
}
}
get isPreventingTransition () {
return this._isPreventingTransition;
}
set isPreventingTransition (value) {
if (this._isPreventingTransition === value) return;
if (value) this.addClass(api.internals.motion.TransitionSelector.NONE);
else this.removeClass(api.internals.motion.TransitionSelector.NONE);
this._isPreventingTransition = value === true;
}
translate (direction, initial) {
this.isPreventingTransition = initial;
this.direction = direction;
}
reset () {
if (this.group) this.group.retrieve(true);
}
_electPrimaries (candidates) {
if (!this.group || !this.group.list) return [];
return super._electPrimaries(candidates).filter(candidate => this.group.list.node.contains(candidate.node));
}
}
const TabKeys = {
LEFT: 'tab_keys_left',
RIGHT: 'tab_keys_right',
HOME: 'tab_keys_home',
END: 'tab_keys_end'
};
const TabEmission = {
PRESS_KEY: api.internals.ns.emission('tab', 'press_key'),
LIST_HEIGHT: api.internals.ns.emission('tab', 'list_height')
};
/**
* TabGroup est la classe étendue de DiscosuresGroup
* Correspond à un objet Tabs avec plusieurs tab-button & Tab (panel)
*/
class TabsGroup extends api.core.DisclosuresGroup {
constructor () {
super('TabPanel');
}
static get instanceClassName () {
return 'TabsGroup';
}
init () {
super.init();
this.listen('transitionend', this.transitionend.bind(this));
this.addAscent(TabEmission.PRESS_KEY, this.pressKey.bind(this));
this.addAscent(TabEmission.LIST_HEIGHT, this.setListHeight.bind(this));
this.isRendering = true;
}
getIndex (defaultIndex = 0) {
super.getIndex(defaultIndex);
}
get list () {
return this.element.getDescendantInstances('TabsList', 'TabsGroup', true)[0];
}
setListHeight (value) {
this.listHeight = value;
}
transitionend (e) {
this.isPreventingTransition = true;
}
get buttonHasFocus () {
return this.members.some(member => member.buttonHasFocus);
}
pressKey (key) {
switch (key) {
case TabKeys.LEFT:
this.pressLeft();
break;
case TabKeys.RIGHT:
this.pressRight();
break;
case TabKeys.HOME:
this.pressHome();
break;
case TabKeys.END:
this.pressEnd();
break;
}
}
/**
* Selectionne l'element suivant de la liste si on est sur un bouton
* Si on est à la fin on retourne au début
*/
pressRight () {
if (this.buttonHasFocus) {
if (this.index < this.length - 1) {
this.index++;
} else {
this.index = 0;
}
this.focus();
}
};
/**
* Selectionne l'element précédent de la liste si on est sur un bouton
* Si on est au debut retourne a la fin
*/
pressLeft () {
if (this.buttonHasFocus) {
if (this.index > 0) {
this.index--;
} else {
this.index = this.length - 1;
}
this.focus();
}
};
/**
* Selectionne le permier element de la liste si on est sur un bouton
*/
pressHome () {
if (this.buttonHasFocus) {
this.index = 0;
this.focus();
}
};
/**
* Selectionne le dernier element de la liste si on est sur un bouton
*/
pressEnd () {
if (this.buttonHasFocus) {
this.index = this.length - 1;
this.focus();
}
};
focus () {
if (this.current) {
this.current.focus();
}
}
apply () {
for (let i = 0; i < this._index; i++) this.members[i].translate(TabPanelDirection.START);
if (this.current) this.current.translate(TabPanelDirection.NONE);
for (let i = this._index + 1; i < this.length; i++) this.members[i].translate(TabPanelDirection.END);
this.isPreventingTransition = false;
}
get isPreventingTransition () {
return this._isPreventingTransition;
}
set isPreventingTransition (value) {
if (this._isPreventingTransition === value) return;
if (value) this.addClass(api.internals.motion.TransitionSelector.NONE);
else this.removeClass(api.internals.motion.TransitionSelector.NONE);
this._isPreventingTransition = value === true;
}
render () {
if (this.current === null) return;
this.node.scrollTop = 0;
this.node.scrollLeft = 0;
const paneHeight = Math.round(this.current.node.offsetHeight);
if (this.panelHeight === paneHeight) return;
this.panelHeight = paneHeight;
this.style.setProperty('--tabs-height', (this.panelHeight + this.listHeight) + 'px');
}
}
const FOCALIZE_OFFSET = 16;
const SCROLL_OFFSET = 16; // valeur en px du scroll avant laquelle le shadow s'active ou se desactive
class TabsList extends api.core.Instance {
static get instanceClassName () {
return 'TabsList';
}
init () {
this.listen('scroll', this.scroll.bind(this));
this.listenKey(api.core.KeyCodes.RIGHT, this.ascend.bind(this, TabEmission.PRESS_KEY, TabKeys.RIGHT), true, true);
this.listenKey(api.core.KeyCodes.LEFT, this.ascend.bind(this, TabEmission.PRESS_KEY, TabKeys.LEFT), true, true);
this.listenKey(api.core.KeyCodes.HOME, this.ascend.bind(this, TabEmission.PRESS_KEY, TabKeys.HOME), true, true);
this.listenKey(api.core.KeyCodes.END, this.ascend.bind(this, TabEmission.PRESS_KEY, TabKeys.END), true, true);
this.isResizing = true;
}
focalize (btn) {
const btnRect = btn.getRect();
const listRect = this.getRect();
const actualScroll = this.node.scrollLeft;
if (btnRect.left < listRect.left) this.node.scrollTo(actualScroll - listRect.left + btnRect.left - FOCALIZE_OFFSET, 0);
else if (btnRect.right > listRect.right) this.node.scrollTo(actualScroll - listRect.right + btnRect.right + FOCALIZE_OFFSET, 0);
}
get isScrolling () {
return this._isScrolling;
}
set isScrolling (value) {
if (this._isScrolling === value) return;
this._isScrolling = value;
this.apply();
}
apply () {
if (this._isScrolling) {
this.addClass(TabSelector.SHADOW);
this.scroll();
} else {
this.removeClass(TabSelector.SHADOW_RIGHT);
this.removeClass(TabSelector.SHADOW_LEFT);
this.removeClass(TabSelector.SHADOW);
}
}
/* ajoute la classe fr-table__shadow-left ou fr-table__shadow-right sur fr-table en fonction d'une valeur de scroll et du sens (right, left) */
scroll () {
const scrollLeft = this.node.scrollLeft;
const isMin = scrollLeft <= SCROLL_OFFSET;
const max = this.node.scrollWidth - this.node.clientWidth - SCROLL_OFFSET;
const isMax = Math.abs(scrollLeft) >= max;
const isRtl = document.documentElement.getAttribute('dir') === 'rtl';
const minSelector = isRtl ? TabSelector.SHADOW_RIGHT : TabSelector.SHADOW_LEFT;
const maxSelector = isRtl ? TabSelector.SHADOW_LEFT : TabSelector.SHADOW_RIGHT;
if (isMin) {
this.removeClass(minSelector);
} else {
this.addClass(minSelector);
}
if (isMax) {
this.removeClass(maxSelector);
} else {
this.addClass(maxSelector);
}
}
resize () {
this.isScrolling = this.node.scrollWidth > this.node.clientWidth + SCROLL_OFFSET;
const height = this.getRect().height;
this.setProperty('--tabs-list-height', `${height}px`);
this.ascend(TabEmission.LIST_HEIGHT, height);
}
dispose () {
this.isScrolling = false;
}
}
api.tab = {
TabPanel: TabPanel,
TabButton: TabButton,
TabsGroup: TabsGroup,
TabsList: TabsList,
TabSelector: TabSelector,
TabEmission: TabEmission
};
api.internals.register(api.tab.TabSelector.PANEL, api.tab.TabPanel);
api.internals.register(api.tab.TabSelector.GROUP, api.tab.TabsGroup);
api.internals.register(api.tab.TabSelector.LIST, api.tab.TabsList);
//# sourceMappingURL=tab.module.js.map