diff --git a/package.json b/package.json
index 2351da6..3440c4e 100644
--- a/package.json
+++ b/package.json
@@ -46,6 +46,7 @@
"mini-css-extract-plugin": "^0.4.2",
"portal-vue": "^2.1.7",
"vue-dragscroll": "^1.10.2",
+ "vue-fragment": "^1.5.1",
"vue-removed-hook-mixin": "^0.1.1",
"vue2-leaflet": "^1.0.2",
"vuex": "^3.0.1",
diff --git a/resources/components/tooltip.html b/resources/components/tooltip.html
index e5ebf55..65805b0 100644
--- a/resources/components/tooltip.html
+++ b/resources/components/tooltip.html
@@ -1,7 +1,10 @@
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
diff --git a/resources/styles/_animations.scss b/resources/styles/_animations.scss
index 8dbe350..592f3b1 100644
--- a/resources/styles/_animations.scss
+++ b/resources/styles/_animations.scss
@@ -14,7 +14,16 @@
}
}
-@include vue-animation(fade, 150ms ease-in-out) {
+@include vue-animation(fade, 250ms ease-in-out) {
+ 0% {
+ opacity: 0
+ }
+ 100% {
+ opacity: 1
+ }
+}
+
+@include vue-animation(tooltip, 100ms ease-in-out) {
0% {
opacity: 0
}
diff --git a/resources/ts/app.ts b/resources/ts/app.ts
index 2385e16..24d2987 100644
--- a/resources/ts/app.ts
+++ b/resources/ts/app.ts
@@ -15,16 +15,27 @@ import Vue from "vue";
import Vuex from 'vuex';
import PortalVue from 'portal-vue';
import VueDragscroll from 'vue-dragscroll';
+import { Plugin as VueFragment } from 'vue-fragment';
import { Workbox } from "workbox-window";
import { migrate } from "./store/migrations";
+import { Component } from "vue-property-decorator";
Vue.use(Vuex);
Vue.use(PortalVue);
Vue.use(VueDragscroll);
+Vue.use(VueFragment);
+
+declare module 'vue/types/vue' {
+ interface Vue {
+ $isTouch: boolean;
+ }
+}
Vue.prototype.$isTouch = 'ontouchstart' in window || navigator.msMaxTouchPoints > 0;
+Component.registerHooks(['removed']);
+
// async dependencies
(async function () {
await migrate("vuex");
diff --git a/resources/ts/components/tooltip.ts b/resources/ts/components/tooltip.ts
index 49755be..01a4326 100644
--- a/resources/ts/components/tooltip.ts
+++ b/resources/ts/components/tooltip.ts
@@ -1,34 +1,88 @@
import Vue from 'vue';
import Component from "vue-class-component";
-import { Prop } from "vue-property-decorator";
+import { Prop, Watch } from "vue-property-decorator";
+
+type Events = {
+ [evnet: string]: (...any) => void,
+}
+
+type Trigger = "hover" | "focus";
+
+const longPressTimeout = 1000;
@Component({ template: require('../../components/tooltip.html') })
export class TooltipComponent extends Vue {
@Prop({ type: String, default: "auto" }) public placement: string;
- @Prop({ type: Number, default: 100 }) public delay: number;
+ @Prop({ type: Number, default: 200 }) public delay: number;
+ @Prop({ type: Array, default: ["hover", "focus"]}) public triggers: Trigger[];
public show: boolean = false;
- public element: Element = null;
+ public root: Element = null;
- private _events: { [event: string]: any };
- private _timeout: number;
+ private _events: Events;
mounted() {
- this.$el.parentElement.addEventListener('mouseenter', this._events['mouseenter'] = () => {
- this._timeout = window.setTimeout(() => { this.show = true }, this.delay);
- });
+ this.root = (this.$refs['root'] as HTMLSpanElement).parentElement;
- this.$el.parentElement.addEventListener('mouseleave', this._events['mouseleave'] = () => {
- window.clearTimeout(this._timeout);
- this.show = false
- });
-
- this.element = this.$el.parentElement;
+ this._registerEventListeners();
}
beforeDestroy() {
- this.$el.parentElement.removeEventListener('mouseenter', this._events['mouseenter']);
- this.$el.parentElement.removeEventListener('mouseleave', this._events['mouseleave']);
+ this._removeEventListeners();
+ }
+
+ @Watch('triggers', { immediate: true })
+ updateTriggers() {
+ this._removeEventListeners();
+
+ this._events = {};
+
+ let blocked: boolean = false;
+
+ if (this.triggers.includes("hover")) {
+ let timeout;
+
+ this._events['mouseenter'] = () => {
+ timeout = window.setTimeout(() => { this.show = !blocked }, this.delay);
+ };
+
+ this._events['mouseleave'] = () => {
+ window.clearTimeout(timeout);
+ this.show = false
+ };
+ }
+
+ if (this.triggers.includes("focus") || (this.triggers.includes("hover") && this.$isTouch)) {
+ if (this.$isTouch) {
+ this._events['touchstart'] = () => {
+ // this is to prevent showing tooltips after tap
+ blocked = true;
+ setTimeout(() => blocked = false, longPressTimeout);
+ }
+ }
+
+ this._events['focus'] = () => {
+ this.show = !blocked;
+ };
+
+ this._events['blur'] = () => {
+ this.show = false
+ };
+ }
+
+ this._registerEventListeners();
+ }
+
+ private _registerEventListeners() {
+ for (const [event, handler] of Object.entries(this._events)) {
+ this.root.addEventListener(event, handler);
+ }
+ }
+
+ private _removeEventListeners() {
+ for (const [event, handler] of Object.entries(this._events)) {
+ this.root.removeEventListener(event, handler);
+ }
}
}
diff --git a/resources/ts/components/utils.ts b/resources/ts/components/utils.ts
index c81743e..200e083 100644
--- a/resources/ts/components/utils.ts
+++ b/resources/ts/components/utils.ts
@@ -104,7 +104,7 @@ export class PopperComponent extends Vue {
}
removed() {
- this._popper.destroy();
+ this._popper.destroy()
}
}
diff --git a/yarn.lock b/yarn.lock
index 2f3da73..016780d 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -6453,6 +6453,11 @@ vue-dragscroll@^1.10.2:
resolved "https://registry.yarnpkg.com/vue-dragscroll/-/vue-dragscroll-1.10.2.tgz#34ace3c6aa7a39edc157cac5e9ea1d5b31b1119c"
integrity sha512-fGVw8KP3ggbp49csa1Tbj2my0YuNmZ1zxYYge4QWIypGNHVwd9hResy/v6QF5HxY0a+qd2EBteeBpxtJxFMp5A==
+vue-fragment@^1.5.1:
+ version "1.5.1"
+ resolved "https://registry.yarnpkg.com/vue-fragment/-/vue-fragment-1.5.1.tgz#44c070d55ed1e9a6c698ef57a5c83f64bb06feeb"
+ integrity sha512-ig6eES6TcMBbANW71ylB+AJgRN+Zksb3f50AxjGpAk6hMzqmeuD80qeh4LJP0jVw2dMBMjgRUfIkrvxygoRgtQ==
+
vue-property-decorator@^7.0.0:
version "7.3.0"
resolved "https://registry.yarnpkg.com/vue-property-decorator/-/vue-property-decorator-7.3.0.tgz#d50d67f0b0f1c814f9f2fba36d6eeccbcc62dbb6"