Welcome to mirror list, hosted at ThFree Co, Russian Federation.

sidebar_hover_peek_behavior.vue « components « super_sidebar « javascripts « assets « app - gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
blob: df432a1928acf6050cfaa43c0dc78e1050364e88 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
<script>
import { getCssClassDimensions } from '~/lib/utils/css_utils';
import Tracking from '~/tracking';
import {
  JS_TOGGLE_EXPAND_CLASS,
  SUPER_SIDEBAR_PEEK_OPEN_DELAY,
  SUPER_SIDEBAR_PEEK_CLOSE_DELAY,
  SUPER_SIDEBAR_PEEK_STATE_CLOSED as STATE_CLOSED,
  SUPER_SIDEBAR_PEEK_STATE_WILL_OPEN as STATE_WILL_OPEN,
  SUPER_SIDEBAR_PEEK_STATE_OPEN as STATE_OPEN,
  SUPER_SIDEBAR_PEEK_STATE_WILL_CLOSE as STATE_WILL_CLOSE,
} from '../constants';

export default {
  name: 'SidebarHoverPeek',
  mixins: [Tracking.mixin()],
  props: {
    isMouseOverSidebar: {
      type: Boolean,
      required: false,
      default: false,
    },
  },
  created() {
    // Nothing needs to observe these properties, so they are not reactive.
    this.state = null;
    this.openTimer = null;
    this.closeTimer = null;
    this.xSidebarEdge = null;
    this.isMouseWithinSidebarArea = false;
  },
  async mounted() {
    await this.$nextTick();
    this.xSidebarEdge = getCssClassDimensions('super-sidebar').width;
    document.addEventListener('mousemove', this.onMouseMove);
    document.documentElement.addEventListener('mouseleave', this.onDocumentLeave);
    document
      .querySelector(`.${JS_TOGGLE_EXPAND_CLASS}`)
      .addEventListener('mouseenter', this.onMouseEnter);
    document
      .querySelector(`.${JS_TOGGLE_EXPAND_CLASS}`)
      .addEventListener('mouseleave', this.onMouseLeave);
    this.changeState(STATE_CLOSED);
  },
  beforeDestroy() {
    document.removeEventListener('mousemove', this.onMouseMove);
    document.documentElement.removeEventListener('mouseleave', this.onDocumentLeave);
    document
      .querySelector(`.${JS_TOGGLE_EXPAND_CLASS}`)
      .removeEventListener('mouseenter', this.onMouseEnter);
    document
      .querySelector(`.${JS_TOGGLE_EXPAND_CLASS}`)
      .removeEventListener('mouseleave', this.onMouseLeave);
    this.clearTimers();
  },
  methods: {
    onMouseMove({ clientX }) {
      if (clientX < this.xSidebarEdge) {
        this.isMouseWithinSidebarArea = true;
      } else {
        this.isMouseWithinSidebarArea = false;
        if (!this.isMouseOverSidebar && this.state === STATE_OPEN) {
          this.willClose();
        }
      }
    },
    onDocumentLeave() {
      this.isMouseWithinSidebarArea = false;
      if (this.state === STATE_OPEN) {
        this.willClose();
      } else if (this.state === STATE_WILL_OPEN) {
        this.close();
      }
    },
    onMouseEnter() {
      clearTimeout(this.closeTimer);
      this.willOpen();
    },
    onMouseLeave() {
      clearTimeout(this.openTimer);
      if (this.isMouseWithinSidebarArea || this.isMouseOverSidebar) return;
      this.willClose();
    },
    willClose() {
      this.changeState(STATE_WILL_CLOSE);
      this.closeTimer = setTimeout(this.close, SUPER_SIDEBAR_PEEK_CLOSE_DELAY);
    },
    willOpen() {
      this.changeState(STATE_WILL_OPEN);
      this.openTimer = setTimeout(this.open, SUPER_SIDEBAR_PEEK_OPEN_DELAY);
    },
    open() {
      this.changeState(STATE_OPEN);
      this.clearTimers();
      this.track('nav_hover_peek', {
        label: 'nav_sidebar_toggle',
        property: 'nav_sidebar',
      });
    },
    close() {
      if (this.isMouseWithinSidebarArea) return;
      this.changeState(STATE_CLOSED);
      this.clearTimers();
    },
    clearTimers() {
      clearTimeout(this.closeTimer);
      clearTimeout(this.openTimer);
    },
    /**
     * Switches to the new state, and emits a change event.
     *
     * If the given state is the current state, do nothing.
     *
     * @param {string} state The state to transition to.
     */
    changeState(state) {
      if (this.state === state) return;
      this.state = state;
      this.$emit('change', state);
    },
  },
  render() {
    return null;
  },
};
</script>