pimelon-ui 0.0.84 → 0.0.95

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/package.json +37 -16
  2. package/src/components/Autocomplete.vue +5 -5
  3. package/src/components/Badge.vue +8 -21
  4. package/src/components/Button.vue +4 -2
  5. package/src/components/DatePicker.vue +5 -0
  6. package/src/components/Dialog.vue +6 -7
  7. package/src/components/Dropdown.vue +77 -50
  8. package/src/components/FileUploader.vue +3 -3
  9. package/src/components/Input.vue +3 -2
  10. package/src/components/LoadingIndicator.vue +20 -5
  11. package/src/components/Popover.vue +31 -13
  12. package/src/components/TextEditor/FontColor.vue +108 -0
  13. package/src/components/TextEditor/Menu.vue +3 -1
  14. package/src/components/TextEditor/TextEditor.vue +19 -2
  15. package/src/components/TextEditor/TextEditorFixedMenu.vue +1 -0
  16. package/src/components/TextEditor/commands.js +8 -0
  17. package/src/components/TextEditor/index.js +1 -0
  18. package/src/components/Toast.vue +38 -128
  19. package/src/components/Tooltip.vue +3 -2
  20. package/src/components/toast.js +98 -0
  21. package/src/index.js +18 -5
  22. package/src/resources/documentResource.js +17 -8
  23. package/src/resources/index.js +1 -0
  24. package/src/resources/listResource.js +35 -23
  25. package/src/resources/local.js +6 -0
  26. package/src/resources/plugin.js +9 -20
  27. package/src/resources/resources.js +30 -9
  28. package/src/style.css +1 -1
  29. package/src/utils/config.js +9 -0
  30. package/src/utils/melonRequest.js +105 -0
  31. package/src/utils/pageMeta.js +3 -1
  32. package/src/utils/request.js +49 -0
  33. package/src/components/Modal.vue +0 -67
  34. package/src/components/Spinner.vue +0 -27
  35. package/src/components/SuccessMessage.vue +0 -15
package/package.json CHANGED
@@ -1,13 +1,16 @@
1
1
  {
2
2
  "name": "pimelon-ui",
3
- "version": "0.0.84",
3
+ "version": "0.0.95",
4
4
  "description": "A set of components and utilities for rapid UI development",
5
5
  "main": "./src/index.js",
6
6
  "scripts": {
7
7
  "test": "npx prettier --check ./src",
8
8
  "prettier": "npx prettier -w ./src",
9
9
  "prepare": "husky install",
10
- "bump-and-release": "git pull --rebase origin main && yarn version --patch && git push && git push --tags"
10
+ "bump-and-release": "git pull --rebase origin main && yarn version --patch && git push && git push --tags",
11
+ "docs:dev": "vitepress dev docs",
12
+ "docs:build": "vitepress build docs",
13
+ "docs:serve": "vitepress serve docs"
11
14
  },
12
15
  "files": [
13
16
  "src"
@@ -21,34 +24,52 @@
21
24
  "dependencies": {
22
25
  "@headlessui/vue": "^1.5.0",
23
26
  "@popperjs/core": "^2.11.2",
24
- "@tailwindcss/forms": "^0.4.0",
27
+ "@tailwindcss/forms": "^0.5.3",
25
28
  "@tailwindcss/typography": "^0.5.0",
26
- "@tiptap/extension-image": "^2.0.0-beta.30",
27
- "@tiptap/extension-link": "^2.0.0-beta.43",
28
- "@tiptap/extension-mention": "^2.0.0-beta.102",
29
- "@tiptap/extension-placeholder": "^2.0.0-beta.53",
30
- "@tiptap/extension-table": "^2.0.0-beta.54",
31
- "@tiptap/extension-table-cell": "^2.0.0-beta.23",
32
- "@tiptap/extension-table-header": "^2.0.0-beta.25",
33
- "@tiptap/extension-table-row": "^2.0.0-beta.22",
34
- "@tiptap/extension-text-align": "^2.0.0-beta.31",
35
- "@tiptap/starter-kit": "^2.0.0-beta.191",
36
- "@tiptap/suggestion": "^2.0.0-beta.195",
37
- "@tiptap/vue-3": "^2.0.0-beta.96",
29
+ "@tiptap/extension-color": "^2.0.0-beta.205",
30
+ "@tiptap/extension-highlight": "^2.0.0-beta.205",
31
+ "@tiptap/extension-image": "^2.0.0-beta.205",
32
+ "@tiptap/extension-link": "^2.0.0-beta.205",
33
+ "@tiptap/extension-mention": "^2.0.0-beta.205",
34
+ "@tiptap/extension-placeholder": "^2.0.0-beta.205",
35
+ "@tiptap/extension-table": "^2.0.0-beta.205",
36
+ "@tiptap/extension-table-cell": "^2.0.0-beta.205",
37
+ "@tiptap/extension-table-header": "^2.0.0-beta.205",
38
+ "@tiptap/extension-table-row": "^2.0.0-beta.205",
39
+ "@tiptap/extension-text-align": "^2.0.0-beta.205",
40
+ "@tiptap/extension-text-style": "^2.0.0-beta.205",
41
+ "@tiptap/extension-typography": "^2.0.0-beta.205",
42
+ "@tiptap/prosemirror-tables": "^1.1.3",
43
+ "@tiptap/starter-kit": "^2.0.0-beta.205",
44
+ "@tiptap/suggestion": "^2.0.0-beta.205",
45
+ "@tiptap/vue-3": "^2.0.0-beta.205",
38
46
  "autoprefixer": "^10.4.2",
39
47
  "feather-icons": "^4.28.0",
40
48
  "idb-keyval": "^6.2.0",
41
49
  "postcss": "^8.4.5",
50
+ "prosemirror-commands": "^1.5.0",
51
+ "prosemirror-dropcursor": "1.5.0",
52
+ "prosemirror-gapcursor": "^1.3.1",
53
+ "prosemirror-history": "^1.3.0",
54
+ "prosemirror-keymap": "^1.2.0",
55
+ "prosemirror-model": "^1.18.3",
56
+ "prosemirror-schema-list": "^1.2.2",
57
+ "prosemirror-state": "^1.4.2",
58
+ "prosemirror-transform": "^1.7.0",
59
+ "prosemirror-view": "^1.29.1",
42
60
  "showdown": "^2.1.0",
43
61
  "socket.io-client": "^4.5.1",
44
62
  "tailwindcss": "^3.0.12",
45
63
  "tippy.js": "^6.3.7"
46
64
  },
47
65
  "devDependencies": {
66
+ "cross-fetch": "^3.1.5",
48
67
  "husky": "^8.0.3",
49
68
  "lint-staged": ">=10",
50
69
  "prettier": "2.7.1",
51
- "prettier-plugin-tailwindcss": "^0.1.13"
70
+ "prettier-plugin-tailwindcss": "^0.1.13",
71
+ "vitepress": "^1.0.0-alpha.29",
72
+ "vue": "^3.2.45"
52
73
  },
53
74
  "lint-staged": {
54
75
  "*.{js,css,md,vue}": "prettier --write"
@@ -6,11 +6,7 @@
6
6
  <ComboboxButton
7
7
  class="flex w-full items-center justify-between rounded-md bg-gray-100 py-1.5 pl-3 pr-2"
8
8
  :class="{ 'rounded-b-none': isComboboxOpen }"
9
- @click="
10
- () => {
11
- openPopover()
12
- }
13
- "
9
+ @click="() => openPopover()"
14
10
  >
15
11
  <span
16
12
  class="overflow-hidden text-ellipsis text-base"
@@ -100,6 +96,8 @@ import {
100
96
  ComboboxButton,
101
97
  } from '@headlessui/vue'
102
98
  import Popover from './Popover.vue'
99
+ import Button from './Button.vue'
100
+ import FeatherIcon from './FeatherIcon.vue'
103
101
 
104
102
  export default {
105
103
  name: 'Autocomplete',
@@ -107,6 +105,8 @@ export default {
107
105
  emits: ['update:modelValue', 'change'],
108
106
  components: {
109
107
  Popover,
108
+ Button,
109
+ FeatherIcon,
110
110
  Combobox,
111
111
  ComboboxInput,
112
112
  ComboboxOptions,
@@ -3,26 +3,14 @@
3
3
  class="inline-block cursor-default rounded-md px-3 py-1 text-xs font-medium"
4
4
  :class="classes"
5
5
  >
6
- <slot>{{ status }}</slot>
6
+ <slot>{{ label }}</slot>
7
7
  </span>
8
8
  </template>
9
9
  <script>
10
- const DEFAULT_COLOR_MAP = {
11
- Pending: 'yellow',
12
- Running: 'yellow',
13
- Success: 'green',
14
- Failure: 'red',
15
- Active: 'green',
16
- Broken: 'red',
17
- Updating: 'blue',
18
- Rejected: 'red',
19
- Published: 'green',
20
- Approved: 'green',
21
- }
22
-
10
+ let validColors = ['gray', 'red', 'yellow', 'green', 'blue']
23
11
  export default {
24
12
  name: 'Badge',
25
- props: ['color', 'status', 'colorMap'],
13
+ props: ['color', 'label', 'colorMap'],
26
14
  computed: {
27
15
  classes() {
28
16
  let color = this.getBadgeColor()
@@ -41,13 +29,12 @@ export default {
41
29
  methods: {
42
30
  getBadgeColor() {
43
31
  let color = this.color
44
- if (color) {
45
- return color
32
+ if (this.colorMap) {
33
+ color = this.colorMap[this.label]
34
+ }
35
+ if (!color || !validColors.includes(color)) {
36
+ color = 'gray'
46
37
  }
47
-
48
- let statusColorMap = Object.assign(DEFAULT_COLOR_MAP, this.colorMap || {})
49
- color = statusColorMap[this.status] || 'gray'
50
-
51
38
  return color
52
39
  },
53
40
  },
@@ -8,8 +8,10 @@
8
8
  >
9
9
  <LoadingIndicator
10
10
  v-if="loading"
11
- class="mr-2 -ml-1 h-3 w-3"
11
+ class="h-3 w-3"
12
12
  :class="{
13
+ 'mr-2 -ml-1': !icon,
14
+ 'm-0.5': icon,
13
15
  'text-white': appearance == 'primary',
14
16
  'text-gray-600': appearance == 'secondary',
15
17
  'text-red-200': appearance == 'danger',
@@ -24,7 +26,7 @@
24
26
  aria-hidden="true"
25
27
  />
26
28
  <template v-if="loading && loadingText">{{ loadingText }}</template>
27
- <template v-else-if="icon">
29
+ <template v-else-if="icon && !loading">
28
30
  <FeatherIcon :name="icon" class="h-4 w-4" :aria-label="label" />
29
31
  </template>
30
32
  <span v-else :class="icon ? 'sr-only' : ''">
@@ -3,6 +3,7 @@
3
3
  <template #target="{ togglePopover }">
4
4
  <Input
5
5
  type="text"
6
+ icon-left="calendar"
6
7
  :class="inputClass"
7
8
  :value="
8
9
  modelValue && formatValue ? formatValue(modelValue) : modelValue || ''
@@ -92,6 +93,8 @@
92
93
  </template>
93
94
 
94
95
  <script>
96
+ import Input from './Input.vue'
97
+ import FeatherIcon from './FeatherIcon.vue'
95
98
  import Popover from './Popover.vue'
96
99
 
97
100
  export default {
@@ -99,6 +102,8 @@ export default {
99
102
  props: ['modelValue', 'placeholder', 'readonly', 'formatValue', 'inputClass'],
100
103
  emits: ['update:modelValue'],
101
104
  components: {
105
+ Input,
106
+ FeatherIcon,
102
107
  Popover,
103
108
  },
104
109
  data() {
@@ -10,7 +10,7 @@
10
10
  @close="open = false"
11
11
  >
12
12
  <div
13
- class="flex min-h-screen flex-col items-center px-4 pt-4 pb-20 text-center"
13
+ class="flex min-h-screen flex-col items-center px-4 py-4 text-center"
14
14
  :class="dialogPositionClasses"
15
15
  >
16
16
  <TransitionChild
@@ -46,8 +46,8 @@
46
46
  'max-w-3xl': options.size === '3xl',
47
47
  'max-w-2xl': options.size === '2xl',
48
48
  'max-w-xl': options.size === 'xl',
49
- 'max-w-md': options.size === 'md',
50
49
  'max-w-lg': options.size === 'lg' || !options.size,
50
+ 'max-w-md': options.size === 'md',
51
51
  'max-w-sm': options.size === 'sm',
52
52
  'max-w-xs': options.size === 'xs',
53
53
  }"
@@ -60,6 +60,7 @@
60
60
  v-if="icon"
61
61
  class="mx-auto mb-3 flex h-12 w-12 flex-shrink-0 items-center justify-center rounded-full sm:mx-0 sm:mb-0 sm:mr-4 sm:h-9 sm:w-9"
62
62
  :class="{
63
+ 'bg-gray-100': !icon.appearance,
63
64
  'bg-yellow-100': icon.appearance === 'warning',
64
65
  'bg-blue-100': icon.appearance === 'info',
65
66
  'bg-red-100': icon.appearance === 'danger',
@@ -68,8 +69,9 @@
68
69
  >
69
70
  <FeatherIcon
70
71
  :name="icon.name"
71
- class="h-6 w-6 text-red-600 sm:h-5 sm:w-5"
72
+ class="h-6 w-6 sm:h-5 sm:w-5"
72
73
  :class="{
74
+ 'text-gray-600': !icon.appearance,
73
75
  'text-yellow-600': icon.appearance === 'warning',
74
76
  'text-blue-600': icon.appearance === 'info',
75
77
  'text-red-600': icon.appearance === 'danger',
@@ -198,10 +200,7 @@ export default {
198
200
 
199
201
  let icon = this.options.icon
200
202
  if (typeof icon === 'string') {
201
- icon = {
202
- name: icon,
203
- type: 'info',
204
- }
203
+ icon = { name: icon }
205
204
  }
206
205
  return icon
207
206
  },
@@ -1,49 +1,49 @@
1
1
  <template>
2
2
  <Menu as="div" class="relative inline-block text-left" v-slot="{ open }">
3
- <MenuButton as="div">
4
- <slot v-if="$slots.default" v-bind="{ open }"></slot>
5
- <Button v-else v-bind="button" :active="open">
6
- {{ button ? button?.label || null : 'Options' }}
7
- </Button>
8
- </MenuButton>
3
+ <Popover :transition="dropdownTransition" :show="open">
4
+ <template #target>
5
+ <MenuButton as="div">
6
+ <slot v-if="$slots.default" v-bind="{ open }" />
7
+ <Button v-else :active="open" v-bind="button">
8
+ {{ button ? button?.label || null : 'Options' }}
9
+ </Button>
10
+ </MenuButton>
11
+ </template>
9
12
 
10
- <transition
11
- enter-active-class="transition duration-100 ease-out"
12
- enter-from-class="transform scale-95 opacity-0"
13
- enter-to-class="transform scale-100 opacity-100"
14
- leave-active-class="transition duration-75 ease-in"
15
- leave-from-class="transform scale-100 opacity-100"
16
- leave-to-class="transform scale-95 opacity-0"
17
- >
18
- <MenuItems
19
- class="min-w-40 absolute z-10 mt-2 divide-y divide-gray-100 rounded-md bg-white shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none"
20
- :class="{
21
- 'left-0 origin-top-left': placement == 'left',
22
- 'right-0 origin-top-right': placement == 'right',
23
- 'inset-x-0 origin-top': placement == 'center',
24
- }"
25
- >
26
- <div v-for="group in groups" :key="group.key" class="px-1 py-1">
27
- <div
28
- v-if="group.group && !group.hideLabel"
29
- class="px-2 py-1 text-xs font-semibold uppercase tracking-wider text-gray-500"
30
- >
31
- {{ group.group }}
32
- </div>
33
- <MenuItem
34
- v-for="item in group.items"
35
- :key="item.label"
36
- v-slot="{ active }"
37
- >
38
- <button
39
- :class="[
40
- active ? 'bg-gray-100' : 'text-gray-900',
41
- 'group flex w-full items-center rounded-md px-2 py-2 text-sm',
42
- ]"
43
- @click="item.onClick"
13
+ <template #body>
14
+ <MenuItems
15
+ class="min-w-40 mt-2 divide-y divide-gray-100 rounded-md bg-white shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none"
16
+ :class="{
17
+ 'left-0 origin-top-left': placement == 'left',
18
+ 'right-0 origin-top-right': placement == 'right',
19
+ 'inset-x-0 origin-top': placement == 'center',
20
+ }"
21
+ >
22
+ <div v-for="group in groups" :key="group.key" class="px-1 py-1">
23
+ <div
24
+ v-if="group.group && !group.hideLabel"
25
+ class="px-2 py-1 text-xs font-semibold uppercase tracking-wider text-gray-500"
26
+ >
27
+ {{ group.group }}
28
+ </div>
29
+ <MenuItem
30
+ v-for="item in group.items"
31
+ :key="item.label"
32
+ v-slot="{ active }"
44
33
  >
45
- <component :is="item.component" v-if="item.component" />
46
- <template v-else>
34
+ <component
35
+ v-if="item.component"
36
+ :is="item.component"
37
+ :active="active"
38
+ />
39
+ <button
40
+ v-else
41
+ :class="[
42
+ active ? 'bg-gray-100' : 'text-gray-900',
43
+ 'group flex w-full items-center rounded-md px-2 py-2 text-sm',
44
+ ]"
45
+ @click="item.onClick"
46
+ >
47
47
  <FeatherIcon
48
48
  v-if="item.icon"
49
49
  :name="item.icon"
@@ -53,28 +53,45 @@
53
53
  <span class="whitespace-nowrap">
54
54
  {{ item.label }}
55
55
  </span>
56
- </template>
57
- </button>
58
- </MenuItem>
59
- </div>
60
- </MenuItems>
61
- </transition>
56
+ </button>
57
+ </MenuItem>
58
+ </div>
59
+ </MenuItems>
60
+ </template>
61
+ </Popover>
62
62
  </Menu>
63
63
  </template>
64
64
 
65
65
  <script>
66
66
  import { Menu, MenuButton, MenuItems, MenuItem } from '@headlessui/vue'
67
+ import Popover from './Popover.vue'
68
+ import Button from './Button.vue'
67
69
  import FeatherIcon from './FeatherIcon.vue'
68
70
 
69
71
  export default {
70
- name: 'NewDropdown',
71
- props: ['button', 'options', 'placement'],
72
+ name: 'Dropdown',
73
+ props: {
74
+ button: {
75
+ type: Object,
76
+ default: null,
77
+ },
78
+ options: {
79
+ type: Array,
80
+ default: () => [],
81
+ },
82
+ placement: {
83
+ type: String,
84
+ default: 'left',
85
+ },
86
+ },
72
87
  components: {
73
88
  Menu,
74
89
  MenuButton,
75
90
  MenuItems,
76
91
  MenuItem,
92
+ Button,
77
93
  FeatherIcon,
94
+ Popover,
78
95
  },
79
96
  methods: {
80
97
  normalizeDropdownItem(option) {
@@ -113,6 +130,16 @@ export default {
113
130
  }
114
131
  })
115
132
  },
133
+ dropdownTransition() {
134
+ return {
135
+ enterActiveClass: 'transition duration-100 ease-out',
136
+ enterFromClass: 'transform scale-95 opacity-0',
137
+ enterToClass: 'transform scale-100 opacity-100',
138
+ leaveActiveClass: 'transition duration-75 ease-in',
139
+ leaveFromClass: 'transform scale-100 opacity-100',
140
+ leaveToClass: 'transform scale-95 opacity-0',
141
+ }
142
+ },
116
143
  },
117
144
  }
118
145
  </script>
@@ -130,7 +130,7 @@ class FileUploader {
130
130
 
131
131
  export default {
132
132
  name: 'FileUploader',
133
- props: ['fileTypes', 'uploadArgs', 'type', 'validateFile'],
133
+ props: ['fileTypes', 'uploadArgs', 'validateFile'],
134
134
  data() {
135
135
  return {
136
136
  uploader: null,
@@ -204,11 +204,11 @@ export default {
204
204
  .catch((error) => {
205
205
  this.uploading = false
206
206
  let errorMessage = 'Error Uploading File'
207
- if (error._server_messages) {
207
+ if (error?._server_messages) {
208
208
  errorMessage = JSON.parse(
209
209
  JSON.parse(error._server_messages)[0]
210
210
  ).message
211
- } else if (error.exc) {
211
+ } else if (error?.exc) {
212
212
  errorMessage = JSON.parse(error.exc)[0].split('\n').slice(-2, -1)[0]
213
213
  }
214
214
  this.error = errorMessage
@@ -49,7 +49,7 @@
49
49
  ref="input"
50
50
  :value="passedInputValue"
51
51
  :disabled="disabled"
52
- :rows="rows || 3"
52
+ :rows="rows"
53
53
  ></textarea>
54
54
  <select
55
55
  v-if="type === 'select'"
@@ -80,7 +80,7 @@
80
80
  </template>
81
81
 
82
82
  <script>
83
- import { debounce } from 'pimelon-ui'
83
+ import { debounce } from '../index'
84
84
  import FeatherIcon from './FeatherIcon.vue'
85
85
 
86
86
  export default {
@@ -129,6 +129,7 @@ export default {
129
129
  },
130
130
  rows: {
131
131
  type: Number,
132
+ default: 3,
132
133
  },
133
134
  placeholder: {
134
135
  type: String,
@@ -1,12 +1,27 @@
1
1
  <template>
2
- <Spinner class="max-w-xs" />
2
+ <svg
3
+ class="max-w-xs animate-spin"
4
+ xmlns="http://www.w3.org/2000/svg"
5
+ fill="none"
6
+ viewBox="0 0 24 24"
7
+ >
8
+ <circle
9
+ class="opacity-25"
10
+ cx="12"
11
+ cy="12"
12
+ r="10"
13
+ stroke="currentColor"
14
+ stroke-width="4"
15
+ ></circle>
16
+ <path
17
+ class="opacity-75"
18
+ fill="currentColor"
19
+ d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
20
+ ></path>
21
+ </svg>
3
22
  </template>
4
23
  <script>
5
- import Spinner from './Spinner.vue'
6
24
  export default {
7
25
  name: 'LoadingIndicator',
8
- components: {
9
- Spinner,
10
- },
11
26
  }
12
27
  </script>
@@ -20,7 +20,8 @@
20
20
  :class="popoverClass"
21
21
  class="popover-container relative z-[100]"
22
22
  :style="{ minWidth: targetWidth ? targetWidth + 'px' : null }"
23
- v-show="isOpen"
23
+ @mouseover="pointerOverTargetOrPopup = true"
24
+ @mouseleave="onMouseleave"
24
25
  >
25
26
  <transition v-bind="popupTransition">
26
27
  <div v-show="isOpen">
@@ -66,7 +67,10 @@ export default {
66
67
  type: Number,
67
68
  default: 0,
68
69
  },
69
- right: Boolean,
70
+ leaveDelay: {
71
+ type: Number,
72
+ default: 0,
73
+ },
70
74
  placement: {
71
75
  type: String,
72
76
  default: 'bottom-start',
@@ -85,6 +89,7 @@ export default {
85
89
  return {
86
90
  showPopup: false,
87
91
  targetWidth: null,
92
+ pointerOverTargetOrPopup: false,
88
93
  }
89
94
  },
90
95
  watch: {
@@ -97,6 +102,7 @@ export default {
97
102
  },
98
103
  },
99
104
  created() {
105
+ if (typeof window === 'undefined') return
100
106
  if (!document.getElementById('melonui-popper-root')) {
101
107
  const root = document.createElement('div')
102
108
  root.id = 'melonui-popper-root'
@@ -195,24 +201,22 @@ export default {
195
201
  }
196
202
  },
197
203
  open() {
198
- if (this.isOpen) {
199
- return
200
- }
201
204
  this.isOpen = true
202
205
  this.$nextTick(() => this.setupPopper())
203
206
  },
204
207
  close() {
205
- if (!this.isOpen) {
206
- return
207
- }
208
208
  this.isOpen = false
209
209
  },
210
210
  onMouseover() {
211
- this.mouseover = true
211
+ this.pointerOverTargetOrPopup = true
212
+ if (this.leaveTimer) {
213
+ clearTimeout(this.leaveTimer)
214
+ this.leaveTimer = null
215
+ }
212
216
  if (this.trigger === 'hover') {
213
217
  if (this.hoverDelay) {
214
218
  this.hoverTimer = setTimeout(() => {
215
- if (this.mouseover) {
219
+ if (this.pointerOverTargetOrPopup) {
216
220
  this.open()
217
221
  }
218
222
  }, Number(this.hoverDelay) * 1000)
@@ -221,13 +225,27 @@ export default {
221
225
  }
222
226
  }
223
227
  },
224
- onMouseleave() {
225
- this.mouseover = false
228
+ onMouseleave(e) {
229
+ this.pointerOverTargetOrPopup = false
226
230
  if (this.hoverTimer) {
227
231
  clearTimeout(this.hoverTimer)
232
+ this.hoverTimer = null
228
233
  }
229
234
  if (this.trigger === 'hover') {
230
- this.close()
235
+ if (this.leaveTimer) {
236
+ clearTimeout(this.leaveTimer)
237
+ }
238
+ if (this.leaveDelay) {
239
+ this.leaveTimer = setTimeout(() => {
240
+ if (!this.pointerOverTargetOrPopup) {
241
+ this.close()
242
+ }
243
+ }, Number(this.leaveDelay) * 1000)
244
+ } else {
245
+ if (!this.pointerOverTargetOrPopup) {
246
+ this.close()
247
+ }
248
+ }
231
249
  }
232
250
  },
233
251
  },