import { getDelayMs } from 'sesajs/date'
import { j3pAddElt, j3pDetruit, j3pElement, j3pGetPos, j3pIsHtmlElement } from 'src/legacy/core/functions'
import { j3pAffiche } from 'src/lib/mathquill/functions'
import { legacyStyles } from 'src/legacy/core/StylesJ3p'
import { getCssDimension } from 'sesajs/css'
import './bulleAide.css'
/**
* Crée une bulle d’aide
* Cf r11620 pour une version avec un trigger plus grand (la bulle s’affichait lorsque le curseur approchait du trigger sans être dessus)
* @module legacy/outils/bulleAide/BulleAide
*/
// iife pour déclarer ce que je veux tranquillement
// cf https://developer.mozilla.org/fr/docs/Glossaire/IIFE
// fonctions privées, qui pourront être transformées en listeners avec du bind ou appelée avec call
// (pour fixer leur this sur l’instance bulleAide courante)
/**
* Appelé à chaque show, dans le cas où la bulle se positionne sur le trigger,
* pour éviter que la bulle ne déborde à droite
* @private
*/
function followTrigger () {
const pos = j3pGetPos(this.bulleTrigger)
this.bulleDiv.style.left = (pos.x - j3pElement('MepMG').scrollLeft) + 'px'
this.bulleDiv.style.top = (pos.y - j3pElement('MepMG').scrollTop) + 'px'
const total = document.body.offsetWidth
const reste = total - pos.x
if (reste < this.bulleDiv.offsetWidth) {
// la bulle déborde à droite, on décale vers la gauche
const moitie = this.bulleDiv.offsetWidth / 2
if (moitie < pos.x && moitie < reste) {
// centré sur le trigger, ça rentre
this.bulleDiv.style.left = (pos.x - moitie) + 'px'
if (this.gg) this.bulleDiv.style.left = (pos.x - 200) + 'px'
} else if (this.bulleDiv.offsetWidth < total) {
// on cale à droite sans rétrécir le div
this.bulleDiv.style.left = (pos.x - this.bulleDiv.offsetWidth + reste) + 'px'
if (this.gg) this.bulleDiv.style.left = (pos.x - 200) + 'px'
} else {
if (this.gg) this.bulleDiv.style.left = (pos.x - 200) + 'px'
// on prend toute la place possible
this.bulleDiv.style.left = '0'
this.bulleDiv.style.width = total + 'px'
}
} else {
if (this.gg) {
this.bulleDiv.style.left = (pos.x - 200) + 'px'
}
}
}
/**
* Appelé à chaque show, dans le cas où on est dans un parent filé au constructeur
* @private
*/
function replace () {
const pos = j3pGetPos(this.bulleDiv)
// on vérifie pas de minimum, fallait pas nous filer un conteneur qui peut se retrouver trop à droite…
this.bulleDiv.style.maxWidth = (document.body.offsetWidth - pos.x) + 'px'
}
/**
* La dernière bulle ouverte (pour la refermer si on en ouvre une autre, cette var est globale à l’outil, donc commune à toutes les bulles)
* @private
* @type {BulleAide}
*/
let lastOpened
/**
* Crée l’objet bulle et ses éléments dans le dom
* @param {HTMLElement|string} container Le conteneur du "?" que l’on va ajouter pour servir de déclencheur (ou le déclencheur si options.containerAsTrigger)
* @param {string} content Le contenu de la bulle d’aide (qui sera passé à j3pAffiche pour interpréter les caractères spéciaux façon j3p)
* @param {Object} [options]
* @param {string} [options.backgroundColor]
* @param {string} [options.color]
* @param {boolean} [options.containerAsTrigger] Passer true pour que le container passé soit utilisé comme élément déclencheur (et dans ce cas on ajoute pas le (?) dedans)
* @param {HTMLElement|string} [options.parent] Éventuel parent dans lequel mettre la bulle d’aide
* @param {number} [options.left=0] Pour positionner la bulle d’aide par rapport à son parent
* @param {number} [options.top=0]
* @param {boolean} [options.gg=false]
* @constructor
*/
function BulleAide (container, content, options) {
if (typeof container === 'string') container = j3pElement(container)
if (!j3pIsHtmlElement(container)) return console.error(Error('container manquant'))
// les paramètres
if (!options || typeof options !== 'object') options = {}
// on garde des refs sur nos éléments bulleTrigger et bulleDiv pour les utiliser directement dans les listeners
if (options.containerAsTrigger) {
this.bulleTrigger = container
} else {
/**
* Le span contenant le (?) qui sert de déclencheur à l’apparition de la bulle
* @type {HTMLElement}
*/
this.bulleTrigger = j3pAddElt(container, 'span', '', {
style: {
// cf bulleAide.css et https://developer.mozilla.org/fr/docs/Web/CSS/animation
animation: 'bulleAideGlow 2s infinite',
color: legacyStyles.colorCorrection
}
})
// et on ajoute le contenu en gras dedans
j3pAddElt(this.bulleTrigger, 'strong', '(?)')
}
/**
* c’est quoi ce gg ???
* @type {boolean}
*/
this.gg = options.gg === true
// le div de la bulle
const divStyle = {
padding: '0.5em',
border: 'solid 2px',
display: 'none',
position: 'absolute',
// en haut à droite, du parent, on décale un peu
left: options.left ? getCssDimension(options.left) : '0',
top: options.top ? getCssDimension(options.top) : '0',
backgroundColor: options.backgroundColor || options.couleurfond || '#81BEF7',
color: options.color || options.couleur || '#0404B4',
borderRadius: '5px',
verticalAlign: 'middle',
// 50 pour les boutons, 80 pour les modales, 100 pour dialog, faut être au-desssus…
zIndex: 110
}
// on regarde si on a fourni un parent
let parent = options.parent
if (typeof parent === 'string') parent = j3pElement(parent)
if (parent) {
this.replace = replace.bind(this)
} else {
// s’il n’est pas fourni on se positionnera dans body, juste sur le span
parent = document.body
this.replace = followTrigger.bind(this)
}
/**
* Le div du contenu de la bulle
* @memberOf BulleAide
* @type {HTMLElement}
*/
this.bulleDiv = j3pAddElt(parent, 'div', '', { style: divStyle })
j3pAffiche(this.bulleDiv, '', content)
this.visible = false
// ces listeners sont attachés à l’instance, mais les fct restent génériques (déclarées une seule fois pour toutes les bulles)
this.listeners = {
show: this.show.bind(this),
hide: this.hide.bind(this),
toggle: this.toggle.bind(this)
}
// hide / show au survol
this.bulleTrigger.addEventListener('mouseenter', this.listeners.show, false)
this.bulleTrigger.addEventListener('mouseout', this.listeners.hide, false)
// pour les tablettes faut du click
this.bulleTrigger.addEventListener('click', this.listeners.toggle, false)
// hide au mouseout du div, au cas où il recouvre le trigger
this.bulleDiv.addEventListener('mouseout', this.listeners.hide, false)
} // BulleAide
BulleAide.prototype.show = function show () {
if (!this.bulleDiv) return console.error(Error('Cette bulle a été détruite'))
if (this.visible) return
// si y’avait une bulle ouverte on la ferme (ça peut arriver sur tablette avec le click)
if (lastOpened) lastOpened.hide()
lastOpened = this
// on affiche le div
this.visible = true
this.bulleDiv.style.display = 'block'
// et on arrête l’animation sur le span
this.bulleTrigger.style.animation = ''
this.lastShowTs = Date.now() // timestamp, en ms
this.replace() // followTrigger ou replace suivant le cas
}
BulleAide.prototype.hide = function hide () {
if (!this.bulleDiv) return console.error(Error('Cette bulle a été détruite'))
if (!this.visible) return
if (getDelayMs(this.lastShowTs, 101) < 100) return // la bulle recouvre le trigger, on laisse tomber ce sera son mouseout qui cachera
// on cache le div
this.visible = false
this.bulleDiv.style.display = 'none'
}
BulleAide.prototype.toggle = function toggle () {
if (this.visible) this.hide()
else this.show()
}
/**
* Détruit la bulle (et son trigger "(?)" que l’on avait ajouté)
*/
BulleAide.prototype.disable = function disable () {
if (lastOpened === this) lastOpened = null
if (!this.bulleDiv) return console.error(Error('Cette bulle a déjà été détruite'))
// this.mainDiv.removeEventListener('mousemove', this.listeners.onMouseMove, false)
// détruire l’élément va détruire ses écouteurs, sauf si qqun a mis une ref dessus (avec un var bulleDiv = bulle.bulleDiv)
// donc on nettoie quand même avant de détruire
this.bulleDiv.removeEventListener('mouseout', this.listeners.hide, false)
j3pDetruit(this.bulleDiv)
this.bulleDiv = null
// idem pour le trigger
this.bulleTrigger.removeEventListener('mouseenter', this.listeners.show, false)
this.bulleTrigger.removeEventListener('mouseout', this.listeners.hide, false)
this.bulleTrigger.removeEventListener('click', this.listeners.toggle, false)
j3pDetruit(this.bulleTrigger)
this.bulleTrigger = null
// et on surcharge aussi cette methode, au cas où on l’appellerait une 2e fois
this.disable = function () {
console.error(Error('Cette bulle d’aide a déjà été désactivée'))
}
}
// et on exporte seulement ça
export default BulleAide