Source: interaction/Hover.js

import Interaction from 'ol/interaction/Interaction'

// code inspiré de ol-ext https://github.com/Viglino/ol-ext/blob/master/src/interaction/Hover.js
// on va dispatch les event directement sur core

/** Interaction qui permet de lever un event lorsque l'on passe sur une feature
 * @constructor
 * @extends {ol_interaction_Interaction}
 * @fires hover, hover-enter, hover-leave (directement sur le viewer)
 * @param {olx.interaction.HoverOptions}
 *  @param { string | undefined } options.cursor css cursor a appliquer lorsque l'on survole une feature
 *  @param {function | undefined} options.featureFilter fonction filtre avec deux arguments, la feature et son layer . Return true pour valider le hover de la feature
 *  @param {number | undefined} options.hitTolerance Hit-detection tolerance en pixels.
 *  @param { function | undefined } options.handleCancelHover fonction a appeler pour empecher globalement le survol
 *  @param { function | undefined } options.handleCancelPropagation fonction a appeler pour empêcher la propagation des events apres cette interactions
 */
class Hover extends Interaction {
  constructor (options) {
    options = options || { }

    // on utilise self car on ne peut pas utiliser this avant la méthode super()
    let self = null

    const optOptions = {
      ...options,
      handleEvent: (evt) => {
        if (!self.getActive() || evt.dragging) { return true }

        //  si certaines fonctions internes sont actives, on quitte les feature "hover" et on ne fait rien
        // il faudra surement éttofer au fur et a mesurre du temps
        if (self.viewer.dataLayer.hasNonHoverableInteractionInProgress()) {
          self.unHoverLastFeature()
          return true
        }

        if (options.handleCancelHover && options.handleCancelHover(evt)) { return true }

        if (evt.type === 'pointermove') {
          self.handleMove_(evt)
        }
        // l'utilisateur de cette api peut empecher de propager les event
        if (options.handleCancelPropagation) { return options.handleCancelPropagation(evt) }
        return true
      },
    }

    super(optOptions)

    self = this

    if (!options.viewer) {
      console.error('[CustomHover] options.viewer non présent')
      return
    }
    this.viewer = options.viewer

    // dernière feature survolée
    this.lastFeature_ = null
    // et son calque
    this.lastLayer_ = null

    this.layerFilter_ = (layer) => {
      // seulement si on a défini le calque comme hoverable
      return this.viewer.dataLayer.isHoverableLayer(layer)
    }
    this.setFeatureFilter(options.featureFilter)
    this.set('hitTolerance', options.hitTolerance)
    this.setCursor(options.cursor)
  }

  /**
   * Remove the interaction from its current map, if any,  and attach it to a new
   * map, if any. Pass `null` to just remove the interaction from the current map.
   * @param {ol.Map} map Map.
   * @api stable
   */
  setMap (map) {
    if (this.previousCursor_ !== undefined && this.getMap()) {
      this.getMap().getTargetElement().style.cursor = this.previousCursor_
      this.previousCursor_ = undefined
    }
    super.setMap(map)
  }

  /** Activate / deactivate interaction
     * @param {boolean} b
     */
  setActive (b) {
    super.setActive(b)
    if (this.cursor_ && this.getMap() && this.getMap().getTargetElement()) {
      const style = this.getMap().getTargetElement().style
      if (this.previousCursor_ !== undefined) {
        style.cursor = this.previousCursor_
        this.previousCursor_ = undefined
      }
    }
  }

  /**
     * Set cursor on hover
     * @param { string } cursor css cursor propertie or a function that gets a feature, default: none
     * @api stable
     */
  setCursor (cursor) {
    if (!cursor && this.previousCursor_ !== undefined && this.getMap()) {
      this.getMap().getTargetElement().style.cursor = this.previousCursor_
      this.previousCursor_ = undefined
    }
    this.cursor_ = cursor
  }

  /** Feature filter to get only one feature
  * @param {function} filter a function with two arguments, the feature and the layer of the feature. Return true to select the feature
  */
  setFeatureFilter (filter) {
    if (typeof (filter) === 'function') { this.featureFilter_ = filter } else { this.featureFilter_ = function () { return true } }
  }

  /** Feature filter to get only one feature
  * @param {function} filter a function with one argument, the layer to test. Return true to test the layer
  */
  /* setLayerFilter (filter) {
    if (typeof (filter) === 'function') { this.layerFilter_ = filter } else { this.layerFilter_ = function () { return true } }
  } */

  /** Get features whenmove
  * @param {ol.event} e "move" event
  */
  handleMove_ (e) {
    const map = this.getMap()
    if (map) {
      let feature, layer
      const b = map.forEachFeatureAtPixel(
        e.pixel,
        (f, l) => {
          if (this.featureFilter_.call(null, f, l)) {
            feature = f
            layer = l
            return true
          } else {
            feature = layer = null
            return false
          }
        }, {
          hitTolerance: this.get('hitTolerance'),
          layerFilter: this.layerFilter_,
        }
      )

      if (b) {
        this.hoverFeature(e, feature, layer)
      }

      if (this.lastFeature_ === feature && this.lastLayer_ === layer) {
        // on est sur la même feature, on ne génère par d'event
      } else {
        // on quitte la dernière feature avant de faire un enter
        if (this.lastFeature_) {
          this.leaveFeature(e, this.lastFeature_, this.lastLayer_)
        }

        if (feature) {
          this.enterFeature(e, feature, layer)
        }
      }

      if (this.cursor_) {
        const style = map.getTargetElement().style
        if (b) {
          if (style.cursor !== this.cursor_) {
            this.previousCursor_ = style.cursor
            style.cursor = this.cursor_
          }
        } else if (this.previousCursor_ !== undefined) {
          style.cursor = this.previousCursor_
          this.previousCursor_ = undefined
        }
      }
    }
  }

  hoverFeature (e, feature, layer) {
    // on fait du hover sur une feature, on dispatch un event
    this.viewer.dispatchEvent(
      'hover',
      {
        feature,
        layer,
        coordinate: e.coordinate,
        pixel: e.pixel,
        map: e.map,
        originalEvent: e.originalEvent,
        dragging: e.dragging,
      })
  }

  leaveFeature (e, feature, layer) {
    if (feature) {
      this.viewer.dataLayer.removeTagToOlFeature(feature, this.viewer.dataLayer.systemTagName.HOVERED_FEATURE)
    }
    this.viewer.dispatchEvent(
      'hover-leave',
      {
        feature,
        layer,
        coordinate: e.coordinate,
        pixel: e.pixel,
        map: e.map,
        originalEvent: e.originalEvent,
        dragging: e.dragging,
      })

    this.lastFeature_ = null
    this.lastLayer_ = null
  }

  enterFeature (e, feature, layer) {
    this.viewer.dataLayer.addTagToOlFeature(feature, this.viewer.dataLayer.systemTagName.HOVERED_FEATURE)
    this.lastFeature_ = feature
    this.lastLayer_ = layer
    this.viewer.dispatchEvent(
      'hover-enter',
      {
        feature,
        layer,
        coordinate: e.coordinate,
        pixel: e.pixel,
        map: e.map,
        originalEvent: e.originalEvent,
        dragging: e.dragging,
      })
  }

  unHoverLastFeature () {
    if (this.lastFeature_) {
      this.leaveFeature({}, this.lastFeature_, this.lastLayer_)
    }
  }
}

export default Hover