@maas/vue-equipment 0.30.1 → 0.30.3

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 (27) hide show
  1. package/dist/nuxt/module.json +1 -1
  2. package/dist/plugins/MagicCommand/src/components/MagicCommandItem.vue +2 -2
  3. package/dist/plugins/MagicCommand/src/components/MagicCommandView.vue +1 -1
  4. package/dist/plugins/MagicMenu/src/components/MagicMenuContent.vue +77 -14
  5. package/dist/plugins/MagicMenu/src/components/MagicMenuFloat.vue +2 -2
  6. package/dist/plugins/MagicMenu/src/components/MagicMenuItem.vue +1 -2
  7. package/dist/plugins/MagicMenu/src/components/MagicMenuProvider.vue +1 -5
  8. package/dist/plugins/MagicMenu/src/components/MagicMenuRemote.vue +12 -3
  9. package/dist/plugins/MagicMenu/src/components/MagicMenuTrigger.vue +21 -31
  10. package/dist/plugins/MagicMenu/src/components/MagicMenuView.vue +0 -1
  11. package/dist/plugins/MagicMenu/src/composables/private/useMenuCallback.d.ts +2 -1
  12. package/dist/plugins/MagicMenu/src/composables/private/useMenuCallback.mjs +2 -1
  13. package/dist/plugins/MagicMenu/src/composables/private/useMenuChannel.mjs +0 -3
  14. package/dist/plugins/MagicMenu/src/composables/private/useMenuCursor.d.ts +12 -0
  15. package/dist/plugins/MagicMenu/src/composables/private/useMenuCursor.mjs +173 -0
  16. package/dist/plugins/MagicMenu/src/composables/private/useMenuItem.mjs +2 -2
  17. package/dist/plugins/MagicMenu/src/composables/private/useMenuKeyListener.mjs +4 -4
  18. package/dist/plugins/MagicMenu/src/composables/private/useMenuTrigger.d.ts +1 -3
  19. package/dist/plugins/MagicMenu/src/composables/private/useMenuTrigger.mjs +2 -180
  20. package/dist/plugins/MagicMenu/src/composables/private/useMenuView.d.ts +2 -1
  21. package/dist/plugins/MagicMenu/src/composables/private/useMenuView.mjs +24 -13
  22. package/dist/plugins/MagicMenu/src/composables/useMagicMenu.d.ts +1 -1
  23. package/dist/plugins/MagicMenu/src/composables/useMagicMenu.mjs +1 -1
  24. package/dist/plugins/MagicMenu/src/types/index.d.ts +1 -5
  25. package/dist/plugins/MagicMenu/src/utils/defaultOptions.mjs +3 -3
  26. package/dist/plugins/MagicPlayer/src/components/MagicPlayer.vue.d.ts +1 -1
  27. package/package.json +1 -1
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@maas/vue-equipment/nuxt",
3
3
  "configKey": "vueEquipment",
4
- "version": "0.30.0",
4
+ "version": "0.30.2",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "0.8.0",
7
7
  "unbuild": "unknown"
@@ -19,8 +19,8 @@ import {
19
19
  onMounted,
20
20
  onUnmounted,
21
21
  } from 'vue'
22
- import { useEventListener, onKeyStroke } from '@vueuse/core'
23
22
  import { uuid } from '@maas/vue-equipment/utils'
23
+ import { useEventListener, onKeyStroke } from '@vueuse/core'
24
24
  import { useCommandStore } from '../composables/private/useCommandStore'
25
25
  import { useCommandItem } from '../composables/private/useCommandItem'
26
26
  import { MagicCommandInstanceId } from '../symbols'
@@ -43,7 +43,7 @@ const commandId = inject(MagicCommandInstanceId, '')
43
43
  const { selectItem, activeItem } = useCommandItem(commandId)
44
44
 
45
45
  const mappedId = computed(() => {
46
- return props.id || uuid()
46
+ return props.id ?? uuid()
47
47
  })
48
48
 
49
49
  const isActive = computed(() => {
@@ -34,7 +34,7 @@ const elRef = ref<HTMLElement | undefined>(undefined)
34
34
  const { activeView, selectView } = useCommandView()
35
35
 
36
36
  const mappedId = computed(() => {
37
- return props.id || uuid()
37
+ return props.id ?? uuid()
38
38
  })
39
39
 
40
40
  const isActive = computed(() => {
@@ -31,6 +31,20 @@
31
31
  </magic-menu-float>
32
32
  </div>
33
33
  </transition>
34
+ <span
35
+ v-for="point in coords"
36
+ :style="{
37
+ background: 'red',
38
+ position: 'fixed',
39
+ top: point.y + 'px',
40
+ left: point.x + 'px',
41
+ width: '4px',
42
+ height: '4px',
43
+ zIndex: 1000,
44
+ pointerEvents: 'none',
45
+ transform: 'translate(-50%, -50%)',
46
+ }"
47
+ />
34
48
  </teleport>
35
49
  </template>
36
50
 
@@ -44,8 +58,13 @@ import {
44
58
  computed,
45
59
  type MaybeRef,
46
60
  type ComponentPublicInstance,
61
+ onBeforeUnmount,
47
62
  } from 'vue'
48
63
  import { useMenuView } from '../composables/private/useMenuView'
64
+ import { useMenuState } from '../composables/private/useMenuState'
65
+ import { useMenuCallback } from '../composables/private/useMenuCallback'
66
+ import { useMenuDOM } from '../composables/private/useMenuDOM'
67
+ import { useMenuCursor } from '../composables/private/useMenuCursor'
49
68
  import {
50
69
  MagicMenuInstanceId,
51
70
  MagicMenuViewId,
@@ -54,9 +73,6 @@ import {
54
73
 
55
74
  import '@maas/vue-equipment/utils/css/animations/fade-in.css'
56
75
  import '@maas/vue-equipment/utils/css/animations/fade-out.css'
57
- import { useMenuState } from '../composables/private/useMenuState'
58
- import { useMenuCallback } from '../composables/private/useMenuCallback'
59
- import { useMenuDOM } from '../composables/private/useMenuDOM'
60
76
 
61
77
  defineOptions({
62
78
  inheritAttrs: false,
@@ -101,6 +117,10 @@ const mappedTransition = computed(() => {
101
117
  }
102
118
  })
103
119
 
120
+ // Split isActive into two values to animate content smoothly
121
+ const innerActive = ref(false)
122
+ const wrapperActive = ref(false)
123
+
104
124
  const { lockScroll, unlockScroll } = useMenuDOM()
105
125
  const {
106
126
  onBeforeEnter,
@@ -115,27 +135,21 @@ const {
115
135
  viewId,
116
136
  lockScroll,
117
137
  unlockScroll,
138
+ wrapperActive,
118
139
  })
119
140
 
120
- // Split isActive into two values to animate content smoothly
121
- const innerActive = ref(false)
122
- const wrapperActive = ref(false)
123
-
124
141
  // Handle state
125
142
  async function onOpen() {
126
143
  wrapperActive.value = true
127
144
  await nextTick()
128
145
  innerActive.value = true
129
146
  await nextTick()
130
- if (view) {
131
- view.children.content = contentRef.value
132
- }
147
+ initialize()
133
148
  }
134
149
 
135
- async function onClose() {
150
+ function onClose() {
151
+ destroy()
136
152
  innerActive.value = false
137
- await nextTick()
138
- wrapperActive.value = false
139
153
  }
140
154
 
141
155
  watch(
@@ -149,9 +163,58 @@ watch(
149
163
  }
150
164
  )
151
165
 
166
+ // Handle cursor
167
+ const {
168
+ coords,
169
+ destroy,
170
+ initialize,
171
+ isInsideTriangle,
172
+ isInsideTo,
173
+ isInsideFrom,
174
+ } = useMenuCursor(view!)
175
+
176
+ function disableCursor() {
177
+ state.input.disabled = [...state.input.disabled, 'pointer']
178
+ }
179
+
180
+ function enableCursor() {
181
+ state.input.disabled = state.input.disabled.filter((x) => x !== 'pointer')
182
+ }
183
+
184
+ watch(isInsideTriangle, (value) => {
185
+ if (value) {
186
+ disableCursor()
187
+ } else {
188
+ enableCursor()
189
+ }
190
+ })
191
+
192
+ watch(isInsideTo, (value) => {
193
+ if (value) {
194
+ enableCursor()
195
+ }
196
+ })
197
+
198
+ const isOutside = computed(
199
+ () => !isInsideTo.value && !isInsideFrom.value && !isInsideTriangle.value
200
+ )
201
+
202
+ watch(isOutside, (value, oldValue) => {
203
+ if (value && !oldValue) {
204
+ switch (state.options.mode) {
205
+ case 'navigation':
206
+ view!.active = false
207
+ }
208
+ }
209
+ })
210
+
211
+ onBeforeUnmount(() => {
212
+ destroy()
213
+ })
214
+
152
215
  provide(MagicMenuContentId, `${viewId}-content`)
153
216
  </script>
154
217
 
155
218
  <style>
156
- .magic-menu-content{-webkit-user-select:none;-moz-user-select:none;user-select:none}.magic-menu-content__inner{border:0;padding:0}.magic-menu-content__initial-enter-active{animation:fade-in 50ms ease}.magic-menu-content__final-leave-active{animation:fade-out .15s ease}.magic-menu-content__nested-enter-active{animation:fade-in .1s ease}
219
+ .magic-menu-content{-webkit-user-select:none;-moz-user-select:none;user-select:none}.magic-menu-content__inner{border:0;padding:0}.magic-menu-content--initial-enter-active{animation:fade-in 0ms ease}.magic-menu-content--final-leave-active{animation:fade-out .2s ease}.magic-menu-content--nested-enter-active{animation:fade-in .3s ease}
157
220
  </style>
@@ -161,7 +161,7 @@ const mappedReferenceEl = computed(() => {
161
161
  },
162
162
  }
163
163
  } else {
164
- return view?.children?.trigger
164
+ return document.querySelector(`[data-id="${viewId}-trigger"]`)
165
165
  }
166
166
  })
167
167
 
@@ -221,5 +221,5 @@ const polygonPoints = computed(() => {
221
221
  </script>
222
222
 
223
223
  <style>
224
- .magic-menu-float{display:flex;z-index:var(--magic-menu-float-z-index,999)}.magic-menu-float.-top{flex-direction:column-reverse}.magic-menu-float.-bottom{flex-direction:column}.magic-menu-float.-left{flex-direction:row-reverse}.magic-menu-float.-right{flex-direction:row}.magic-menu-float__arrow{color:var(--magic-menu-float-arrow-color,inherit);height:var(--magic-menu-float-arrow-height,.75rem);width:var(--magic-menu-float-arrow-width,.75rem)}.magic-menu-float__arrow svg{height:100%;width:100%}
224
+ .magic-menu-float{display:flex}.magic-menu-float.-top{flex-direction:column-reverse}.magic-menu-float.-bottom{flex-direction:column}.magic-menu-float.-left{flex-direction:row-reverse}.magic-menu-float.-right{flex-direction:row}.magic-menu-float__arrow{color:var(--magic-menu-float-arrow-color,inherit);height:var(--magic-menu-float-arrow-height,.75rem);width:var(--magic-menu-float-arrow-width,.75rem)}.magic-menu-float__arrow svg{height:100%;width:100%}
225
225
  </style>
@@ -53,7 +53,6 @@ if (!viewId) {
53
53
  if (!contentId) {
54
54
  throw new Error('MagicMenuItem must be nested inside MagicMenuContent')
55
55
  }
56
-
57
56
  const mappedId = computed(() => props.id ?? `magic-menu-item-${uuid()}`)
58
57
 
59
58
  // Register item
@@ -93,7 +92,7 @@ function guardedUnselect() {
93
92
  unselectItem(mappedId.value)
94
93
  } else {
95
94
  // If there is a nested active view,
96
- // unselect the item once it is closed
95
+ // unselect the item once it is closed
97
96
  watch(
98
97
  () => nestedView.value?.active,
99
98
  (value) => {
@@ -85,11 +85,7 @@ onClickOutside(
85
85
  unselectAllViews()
86
86
  },
87
87
  {
88
- ignore: [
89
- '.magic-menu-view',
90
- '.magic-menu-item',
91
- '.magic-menu-cursor-blocker',
92
- ],
88
+ ignore: ['.magic-menu-view', '.magic-menu-item'],
93
89
  }
94
90
  )
95
91
 
@@ -11,7 +11,7 @@
11
11
  </template>
12
12
 
13
13
  <script lang="ts" setup>
14
- import { computed, inject } from 'vue'
14
+ import { computed, inject, watch } from 'vue'
15
15
  import { useMenuView } from '../composables/private/useMenuView'
16
16
  import { useMenuChannel } from '../composables/private/useMenuChannel'
17
17
  import { MagicMenuInstanceId, MagicMenuViewId } from '../symbols'
@@ -56,11 +56,11 @@ const mappedTrigger = computed<Interaction[]>(
56
56
  const { getView } = useMenuView(instanceId)
57
57
  const view = getView(viewId)
58
58
 
59
- const { getChannel } = useMenuChannel({
59
+ const { initializeChannel, deleteChannel } = useMenuChannel({
60
60
  instanceId,
61
61
  viewId,
62
62
  })
63
- const channel = getChannel(mappedChannelId.value)
63
+ let channel = initializeChannel({ id: mappedChannelId.value })
64
64
 
65
65
  const { onClick, onMouseenter } = useMenuRemote({
66
66
  viewId,
@@ -68,6 +68,15 @@ const { onClick, onMouseenter } = useMenuRemote({
68
68
  mappedChannelId,
69
69
  mappedTrigger,
70
70
  })
71
+
72
+ watch(
73
+ () => view?.active,
74
+ () => {
75
+ // Reset if parent view changes
76
+ deleteChannel(mappedChannelId.value)
77
+ channel = initializeChannel({ id: mappedChannelId.value })
78
+ }
79
+ )
71
80
  </script>
72
81
 
73
82
  <style>
@@ -8,14 +8,13 @@
8
8
  @click="onClick"
9
9
  @contextmenu="onClick"
10
10
  @mouseenter="onMouseenter"
11
- @mouseleave="onMouseleave"
12
11
  >
13
12
  <slot :is-active="view?.active" :is-disabled="mappedDisabled" />
14
13
  </div>
15
14
  </template>
16
15
 
17
16
  <script lang="ts" setup>
18
- import { computed, inject, onBeforeUnmount, ref, watch } from 'vue'
17
+ import { computed, inject, onBeforeUnmount, ref, toValue, watch } from 'vue'
19
18
  import { useMenuState } from '../composables/private/useMenuState'
20
19
  import { useMenuView } from '../composables/private/useMenuView'
21
20
  import { useMenuItem } from '../composables/private/useMenuItem'
@@ -27,6 +26,7 @@ import {
27
26
  } from '../symbols'
28
27
 
29
28
  import type { Interaction } from '../types'
29
+ import { onKeyStroke } from '@vueuse/core'
30
30
 
31
31
  interface MagicMenuTriggerProps {
32
32
  disabled?: boolean
@@ -68,20 +68,16 @@ const mappedTrigger = computed<Interaction[]>(() => {
68
68
  switch (state.options.mode) {
69
69
  case 'menubar':
70
70
  return view?.parent.item
71
- ? ['mouseenter', 'mouseleave', 'click']
71
+ ? ['mouseenter', 'click']
72
72
  : state.active
73
73
  ? ['mouseenter', 'click']
74
74
  : ['click']
75
75
  case 'dropdown':
76
- return view?.parent.item
77
- ? ['mouseenter', 'mouseleave', 'click']
78
- : ['click']
76
+ return view?.parent.item ? ['mouseenter', 'click'] : ['click']
79
77
  case 'context':
80
- return view?.parent.item
81
- ? ['mouseenter', 'mouseleave', 'click']
82
- : ['right-click']
78
+ return view?.parent.item ? ['mouseenter', 'click'] : ['right-click']
83
79
  case 'navigation':
84
- return ['mouseenter', 'mouseleave']
80
+ return ['mouseenter']
85
81
  }
86
82
  })
87
83
 
@@ -93,32 +89,26 @@ const mappedTabindex = computed(() => {
93
89
  }
94
90
  })
95
91
 
96
- const { initialize, destroy, onMouseenter, onClick, onMouseleave } =
97
- useMenuTrigger({
98
- instanceId,
99
- viewId,
100
- itemId,
101
- mappedDisabled,
102
- mappedTrigger,
103
- elRef,
104
- })
105
-
106
- // Initialize watcher
107
- initialize()
92
+ const { onMouseenter, onClick, onEnter } = useMenuTrigger({
93
+ instanceId,
94
+ viewId,
95
+ itemId,
96
+ mappedDisabled,
97
+ mappedTrigger,
98
+ elRef,
99
+ })
108
100
 
109
101
  watch(
110
- elRef,
111
- (value) => {
112
- if (view && value) {
113
- view.children.trigger = value
102
+ () => view?.active,
103
+ async (value) => {
104
+ if (value) {
105
+ await new Promise((resolve) => requestAnimationFrame(resolve))
106
+ toValue(elRef)?.blur()
114
107
  }
115
- },
116
- { immediate: true }
108
+ }
117
109
  )
118
110
 
119
- onBeforeUnmount(() => {
120
- destroy()
121
- })
111
+ onKeyStroke('Enter', onEnter)
122
112
  </script>
123
113
 
124
114
  <style>
@@ -8,7 +8,6 @@
8
8
  import { computed, inject, onBeforeUnmount, provide } from 'vue'
9
9
  import { uuid } from '@maas/vue-equipment/utils'
10
10
  import { useMenuView } from '../composables/private/useMenuView'
11
-
12
11
  import {
13
12
  MagicMenuInstanceId,
14
13
  MagicMenuViewId,
@@ -1,4 +1,4 @@
1
- import { type MaybeRef } from 'vue';
1
+ import { type MaybeRef, type Ref } from 'vue';
2
2
  import type { MenuState } from '../../types.js';
3
3
  type UseMenuCallbackArgs = {
4
4
  state: MenuState;
@@ -6,6 +6,7 @@ type UseMenuCallbackArgs = {
6
6
  viewId: string;
7
7
  lockScroll: () => void;
8
8
  unlockScroll: () => void;
9
+ wrapperActive: Ref<boolean>;
9
10
  };
10
11
  export declare function useMenuCallback(args: UseMenuCallbackArgs): {
11
12
  onBeforeEnter: (_el: Element) => void;
@@ -1,7 +1,7 @@
1
1
  import { toValue } from "vue";
2
2
  import { useMagicEmitter } from "@maas/vue-equipment/plugins";
3
3
  export function useMenuCallback(args) {
4
- const { state, instanceId, viewId, lockScroll, unlockScroll } = args;
4
+ const { state, instanceId, viewId, lockScroll, unlockScroll, wrapperActive } = args;
5
5
  const emitter = useMagicEmitter();
6
6
  function onBeforeEnter(_el) {
7
7
  emitter.emit("beforeEnter", { id: toValue(instanceId), view: viewId });
@@ -44,6 +44,7 @@ export function useMenuCallback(args) {
44
44
  unlockScroll();
45
45
  break;
46
46
  }
47
+ wrapperActive.value = false;
47
48
  }
48
49
  return {
49
50
  onBeforeEnter,
@@ -7,9 +7,6 @@ export function useMenuChannel(args) {
7
7
  const state = initializeState();
8
8
  const { getView } = useMenuView(instanceId);
9
9
  const view = getView(viewId);
10
- if (!view) {
11
- throw new Error(`View ${viewId} not found`);
12
- }
13
10
  function createChannel(args2) {
14
11
  const { id } = args2;
15
12
  const channel = {
@@ -0,0 +1,12 @@
1
+ import type { MenuView } from '../../types.js';
2
+ export declare function useMenuCursor(view: MenuView): {
3
+ coords: import("vue").Ref<{
4
+ x: number;
5
+ y: number;
6
+ }[]>;
7
+ isInsideFrom: import("vue").Ref<boolean>;
8
+ isInsideTo: import("vue").Ref<boolean>;
9
+ isInsideTriangle: import("vue").Ref<boolean>;
10
+ initialize: () => void;
11
+ destroy: () => void;
12
+ };
@@ -0,0 +1,173 @@
1
+ import { ref } from "vue";
2
+ import { useEventListener } from "@vueuse/core";
3
+ export function useMenuCursor(view) {
4
+ let cancelListener = new AbortController();
5
+ const coords = ref([
6
+ { x: 0, y: 0 },
7
+ { x: 0, y: 0 },
8
+ { x: 0, y: 0 }
9
+ ]);
10
+ const isInsideFrom = ref(false);
11
+ const isInsideTo = ref(false);
12
+ const isInsideTriangle = ref(false);
13
+ function extendTriangle(vertices, pixelAmount) {
14
+ const [a, b, c] = vertices;
15
+ switch (view?.placement) {
16
+ case "bottom":
17
+ case "bottom-start":
18
+ case "bottom-end":
19
+ case "top":
20
+ case "top-start":
21
+ case "top-end":
22
+ switch (true) {
23
+ case (a.y < b.y && a.y < c.y):
24
+ b.y += pixelAmount;
25
+ b.x -= pixelAmount;
26
+ c.y += pixelAmount;
27
+ c.x += pixelAmount;
28
+ break;
29
+ case (a.y > b.y && a.y > c.y):
30
+ b.y -= pixelAmount;
31
+ b.x -= pixelAmount;
32
+ c.y -= pixelAmount;
33
+ c.x += pixelAmount;
34
+ break;
35
+ }
36
+ break;
37
+ case "right":
38
+ case "right-start":
39
+ case "right-end":
40
+ case "left":
41
+ case "left-start":
42
+ case "left-end":
43
+ switch (true) {
44
+ case (a.x < b.x && a.x < c.x):
45
+ b.x += pixelAmount;
46
+ b.y -= pixelAmount;
47
+ c.x += pixelAmount;
48
+ c.y += pixelAmount;
49
+ break;
50
+ case (a.x > b.x && a.x > c.x):
51
+ b.x -= pixelAmount;
52
+ b.y -= pixelAmount;
53
+ c.x -= pixelAmount;
54
+ c.y += pixelAmount;
55
+ break;
56
+ }
57
+ break;
58
+ }
59
+ coords.value = [a, b, c];
60
+ return [a, b, c];
61
+ }
62
+ function isPointInTriangle(args) {
63
+ const { p, a, b, c } = args;
64
+ const v0 = { x: c.x - a.x, y: c.y - a.y };
65
+ const v1 = { x: b.x - a.x, y: b.y - a.y };
66
+ const v2 = { x: p.x - a.x, y: p.y - a.y };
67
+ const dot00 = v0.x * v0.x + v0.y * v0.y;
68
+ const dot01 = v0.x * v1.x + v0.y * v1.y;
69
+ const dot02 = v0.x * v2.x + v0.y * v2.y;
70
+ const dot11 = v1.x * v1.x + v1.y * v1.y;
71
+ const dot12 = v1.x * v2.x + v1.y * v2.y;
72
+ const invDenom = 1 / (dot00 * dot11 - dot01 * dot01);
73
+ const u = (dot11 * dot02 - dot01 * dot12) * invDenom;
74
+ const v = (dot00 * dot12 - dot01 * dot02) * invDenom;
75
+ return u >= 0 && v >= 0 && u + v <= 1;
76
+ }
77
+ function isPointInRectangle(args) {
78
+ const { p, top, left, bottom, right } = args;
79
+ return p.x >= left && p.x <= right && p.y >= top && p.y <= bottom;
80
+ }
81
+ function triangleOverlap(cursor, fromBounding, toBounding) {
82
+ const { top, left, bottom, right } = toBounding;
83
+ const centerPoint = view.click ? view.click : {
84
+ x: (fromBounding.left + fromBounding.right) / 2,
85
+ y: (fromBounding.top + fromBounding.bottom) / 2
86
+ };
87
+ const sidePoints = [];
88
+ switch (view.placement) {
89
+ case "top":
90
+ case "top-start":
91
+ case "top-end":
92
+ case "bottom":
93
+ case "bottom-start":
94
+ case "bottom-end":
95
+ const topDist = Math.abs(top - centerPoint.y);
96
+ const bottomDist = Math.abs(bottom - centerPoint.y);
97
+ const mappedY = topDist < bottomDist ? top : bottom;
98
+ sidePoints.push({ x: left, y: mappedY });
99
+ sidePoints.push({ x: right, y: mappedY });
100
+ break;
101
+ case "right":
102
+ case "right-start":
103
+ case "right-end":
104
+ case "left":
105
+ case "left-start":
106
+ case "left-end":
107
+ const rightDist = Math.abs(right - centerPoint.x);
108
+ const leftDist = Math.abs(left - centerPoint.x);
109
+ const mappedX = rightDist < leftDist ? right : left;
110
+ sidePoints.push({ x: mappedX, y: top });
111
+ sidePoints.push({ x: mappedX, y: bottom });
112
+ break;
113
+ }
114
+ const [a, b, c] = extendTriangle(
115
+ [centerPoint, sidePoints[0], sidePoints[1]],
116
+ 16
117
+ );
118
+ return isPointInTriangle({
119
+ p: cursor,
120
+ a,
121
+ b,
122
+ c
123
+ });
124
+ }
125
+ function elementOverlap(cursor, bounding) {
126
+ const { top, left, bottom, right } = bounding;
127
+ return isPointInRectangle({
128
+ p: cursor,
129
+ top,
130
+ left,
131
+ bottom,
132
+ right
133
+ });
134
+ }
135
+ function onMousemove(e) {
136
+ const from = document.querySelector(
137
+ `[data-id="${view?.id}-trigger"]`
138
+ );
139
+ const to = document.querySelector(
140
+ `[data-id="${view?.id}-content"] .magic-menu-content__inner`
141
+ );
142
+ if (from && to) {
143
+ const cursor = { x: e.clientX, y: e.clientY };
144
+ const fromBounding = from.getBoundingClientRect();
145
+ const toBounding = to.getBoundingClientRect();
146
+ isInsideFrom.value = elementOverlap(cursor, fromBounding);
147
+ isInsideTo.value = elementOverlap(cursor, toBounding);
148
+ isInsideTriangle.value = triangleOverlap(cursor, fromBounding, toBounding);
149
+ }
150
+ }
151
+ function initialize() {
152
+ cancelListener.abort();
153
+ cancelListener = new AbortController();
154
+ useEventListener(document, "mousemove", onMousemove, {
155
+ signal: cancelListener.signal
156
+ });
157
+ }
158
+ function destroy() {
159
+ coords.value = [];
160
+ isInsideFrom.value = false;
161
+ isInsideTo.value = false;
162
+ isInsideTriangle.value = false;
163
+ cancelListener.abort();
164
+ }
165
+ return {
166
+ coords,
167
+ isInsideFrom,
168
+ isInsideTo,
169
+ isInsideTriangle,
170
+ initialize,
171
+ destroy
172
+ };
173
+ }
@@ -5,7 +5,7 @@ export function useMenuItem(args) {
5
5
  const { instanceId, viewId } = args;
6
6
  const { initializeState } = useMenuState(instanceId);
7
7
  const state = initializeState();
8
- const { getView, unselectNonTreeViews } = useMenuView(instanceId);
8
+ const { getView, unselectDescendingViews } = useMenuView(instanceId);
9
9
  const view = getView(viewId);
10
10
  if (!view) {
11
11
  throw new Error(`View ${viewId} not found`);
@@ -53,7 +53,7 @@ export function useMenuItem(args) {
53
53
  if (instance) {
54
54
  instance.active = true;
55
55
  unselectSiblings(id);
56
- unselectNonTreeViews(viewId);
56
+ unselectDescendingViews(viewId);
57
57
  if (view) {
58
58
  state.input.view = view.id;
59
59
  }
@@ -7,7 +7,7 @@ export function useMenuKeyListener(instanceId) {
7
7
  const {
8
8
  selectView,
9
9
  unselectView,
10
- unselectNonTreeViews,
10
+ unselectUnrelatedViews,
11
11
  unselectAllViews,
12
12
  getView,
13
13
  getNextView,
@@ -101,11 +101,11 @@ export function useMenuKeyListener(instanceId) {
101
101
  if (prevIndex >= 0) {
102
102
  const { selectItem } = useMenuItem({ instanceId, viewId });
103
103
  selectItem(enabledItems[prevIndex]?.id);
104
- unselectNonTreeViews(viewId);
104
+ unselectUnrelatedViews(viewId);
105
105
  } else if (prevIndex !== -1) {
106
106
  const { selectItem } = useMenuItem({ instanceId, viewId });
107
107
  selectItem(enabledItems[enabledItems.length - 1]?.id);
108
- unselectNonTreeViews(viewId);
108
+ unselectUnrelatedViews(viewId);
109
109
  }
110
110
  }
111
111
  function onArrowDown(e) {
@@ -125,7 +125,7 @@ export function useMenuKeyListener(instanceId) {
125
125
  if (nextIndex >= 0) {
126
126
  const { selectItem } = useMenuItem({ instanceId, viewId });
127
127
  selectItem(enabledItems[nextIndex]?.id);
128
- unselectNonTreeViews(viewId);
128
+ unselectUnrelatedViews(viewId);
129
129
  }
130
130
  }
131
131
  function onEscape(e) {
@@ -11,8 +11,6 @@ type UseMenuTriggerArgs = {
11
11
  export declare function useMenuTrigger(args: UseMenuTriggerArgs): {
12
12
  onMouseenter: () => void;
13
13
  onClick: (e: MouseEvent) => void;
14
- onMouseleave: (e: MouseEvent) => void;
15
- initialize: () => void;
16
- destroy: () => void;
14
+ onEnter: (e: KeyboardEvent) => void;
17
15
  };
18
16
  export {};
@@ -1,139 +1,14 @@
1
- import {
2
- ref,
3
- computed,
4
- watch,
5
- toValue
6
- } from "vue";
7
- import {
8
- useEventListener,
9
- useElementBounding,
10
- useMagicKeys,
11
- useFocus,
12
- onKeyStroke
13
- } from "@vueuse/core";
1
+ import { useMagicKeys, useFocus } from "@vueuse/core";
14
2
  import { useMenuView } from "./useMenuView.mjs";
15
3
  import { useMenuState } from "./useMenuState.mjs";
16
4
  export function useMenuTrigger(args) {
17
5
  const { instanceId, viewId, itemId, mappedTrigger, mappedDisabled, elRef } = args;
18
- let cancelPointermove = void 0;
19
- const mouseleaveCoordinates = ref(void 0);
20
6
  const { getView, selectView, unselectView } = useMenuView(instanceId);
21
7
  const view = getView(viewId);
22
8
  const { initializeState } = useMenuState(instanceId);
23
9
  const state = initializeState();
24
10
  const { focused } = useFocus(elRef);
25
- const isInsideTriangle = ref(false);
26
- const isInsideTrigger = ref(false);
27
- const isInsideContent = ref(false);
28
- const isOutside = computed(() => {
29
- return !isInsideTrigger.value && !isInsideContent.value && !isInsideTriangle.value;
30
- });
31
11
  const { shift, control } = useMagicKeys();
32
- function resetState() {
33
- mouseleaveCoordinates.value = void 0;
34
- isInsideTriangle.value = false;
35
- }
36
- function disableCursor() {
37
- state.input.disabled = [...state.input.disabled, "pointer"];
38
- }
39
- function enableCursor() {
40
- state.input.disabled = state.input.disabled.filter((x) => x !== "pointer");
41
- }
42
- function extendTriangle(vertices, pixelAmount) {
43
- const centroid = {
44
- x: (vertices[0].x + vertices[1].x + vertices[2].x) / 3,
45
- y: (vertices[0].y + vertices[1].y + vertices[2].y) / 3
46
- };
47
- function extendPoint(vertex) {
48
- const dir = {
49
- x: vertex.x - centroid.x,
50
- y: vertex.y - centroid.y
51
- };
52
- const distance = Math.sqrt(dir.x * dir.x + dir.y * dir.y);
53
- const unitDir = {
54
- x: dir.x / distance,
55
- y: dir.y / distance
56
- };
57
- const scaledDir = {
58
- x: unitDir.x * pixelAmount,
59
- y: unitDir.y * pixelAmount
60
- };
61
- return {
62
- x: vertex.x + scaledDir.x,
63
- y: vertex.y + scaledDir.y
64
- };
65
- }
66
- return [
67
- extendPoint(vertices[0]),
68
- extendPoint(vertices[1]),
69
- extendPoint(vertices[2])
70
- ];
71
- }
72
- function isPointInTriangle(args2) {
73
- const { p, a, b, c } = args2;
74
- const v0 = { x: c.x - a.x, y: c.y - a.y };
75
- const v1 = { x: b.x - a.x, y: b.y - a.y };
76
- const v2 = { x: p.x - a.x, y: p.y - a.y };
77
- const dot00 = v0.x * v0.x + v0.y * v0.y;
78
- const dot01 = v0.x * v1.x + v0.y * v1.y;
79
- const dot02 = v0.x * v2.x + v0.y * v2.y;
80
- const dot11 = v1.x * v1.x + v1.y * v1.y;
81
- const dot12 = v1.x * v2.x + v1.y * v2.y;
82
- const invDenom = 1 / (dot00 * dot11 - dot01 * dot01);
83
- const u = (dot11 * dot02 - dot01 * dot12) * invDenom;
84
- const v = (dot00 * dot12 - dot01 * dot02) * invDenom;
85
- return u >= 0 && v >= 0 && u + v <= 1;
86
- }
87
- function isPointInRectangle(args2) {
88
- const { p, top, left, bottom, right } = args2;
89
- return p.x >= left && p.x <= right && p.y >= top && p.y <= bottom;
90
- }
91
- async function onPointermove(e) {
92
- if (!mouseleaveCoordinates.value || !view?.children.content) {
93
- return;
94
- }
95
- const mouseCoordinates = { x: e.clientX, y: e.clientY };
96
- const { top, left, bottom, right } = useElementBounding(
97
- view.children.content
98
- );
99
- const {
100
- top: topTrigger,
101
- left: leftTrigger,
102
- bottom: bottomTrigger,
103
- right: rightTrigger
104
- } = useElementBounding(elRef);
105
- const rightDistance = Math.abs(right.value - mouseleaveCoordinates.value.x);
106
- const leftDistance = Math.abs(left.value - mouseleaveCoordinates.value.x);
107
- const mappedX = rightDistance < leftDistance ? right : left;
108
- const [a, b, c] = extendTriangle(
109
- [
110
- mouseleaveCoordinates.value,
111
- { x: mappedX.value, y: top.value },
112
- { x: mappedX.value, y: bottom.value }
113
- ],
114
- 16
115
- );
116
- isInsideTriangle.value = isPointInTriangle({
117
- p: mouseCoordinates,
118
- a,
119
- b,
120
- c
121
- });
122
- isInsideContent.value = isPointInRectangle({
123
- p: mouseCoordinates,
124
- top: top.value,
125
- left: left.value,
126
- bottom: bottom.value,
127
- right: right.value
128
- });
129
- isInsideTrigger.value = isPointInRectangle({
130
- p: mouseCoordinates,
131
- top: topTrigger.value,
132
- left: leftTrigger.value,
133
- bottom: bottomTrigger.value,
134
- right: rightTrigger.value
135
- });
136
- }
137
12
  function onRightClick(e) {
138
13
  switch (e.button) {
139
14
  case 2:
@@ -162,8 +37,6 @@ export function useMenuTrigger(args) {
162
37
  }
163
38
  }
164
39
  function onMouseenter() {
165
- cancelPointermove?.();
166
- resetState();
167
40
  if (mappedTrigger.value.includes("mouseenter") && !mappedDisabled.value && viewId && view) {
168
41
  if (mappedTrigger.value[0].includes("mouseenter")) {
169
42
  state.active = true;
@@ -173,9 +46,6 @@ export function useMenuTrigger(args) {
173
46
  if (!itemId) {
174
47
  state.input.view = viewId;
175
48
  }
176
- if (itemId) {
177
- disableCursor();
178
- }
179
49
  }
180
50
  }
181
51
  }
@@ -211,57 +81,9 @@ export function useMenuTrigger(args) {
211
81
  }
212
82
  }
213
83
  }
214
- function onMouseleave(e) {
215
- if (mappedTrigger.value.includes("mouseleave") && viewId) {
216
- mouseleaveCoordinates.value = { x: e.clientX, y: e.clientY };
217
- cancelPointermove = useEventListener("pointermove", onPointermove);
218
- }
219
- }
220
- function initialize() {
221
- watch(
222
- () => view?.active,
223
- async (value) => {
224
- if (value) {
225
- await new Promise((resolve) => requestAnimationFrame(resolve));
226
- toValue(elRef)?.blur();
227
- }
228
- }
229
- );
230
- watch(isInsideTriangle, async (value, oldValue) => {
231
- if (value && !oldValue) {
232
- disableCursor();
233
- } else if (!value && oldValue) {
234
- enableCursor();
235
- }
236
- });
237
- watch(isInsideContent, (value, oldValue) => {
238
- if (value && !oldValue) {
239
- cancelPointermove?.();
240
- resetState();
241
- enableCursor();
242
- }
243
- });
244
- watch(isOutside, (value, oldValue) => {
245
- if (!cancelPointermove) {
246
- return;
247
- }
248
- if (value && !oldValue) {
249
- unselectView(viewId);
250
- cancelPointermove();
251
- enableCursor();
252
- resetState();
253
- }
254
- });
255
- onKeyStroke("Enter", onEnter);
256
- }
257
- function destroy() {
258
- cancelPointermove?.();
259
- }
260
84
  return {
261
85
  onMouseenter,
262
86
  onClick,
263
- onMouseleave,
264
- initialize,
265
- destroy
87
+ onEnter
266
88
  };
267
89
  }
@@ -14,7 +14,8 @@ export declare function useMenuView(instanceId: MaybeRef<string>): {
14
14
  getParentView: (id: string) => MenuView | undefined;
15
15
  selectView: (id: string) => void;
16
16
  unselectView: (id: string) => void;
17
- unselectNonTreeViews: (id: string) => void;
17
+ unselectUnrelatedViews: (id: string) => void;
18
+ unselectDescendingViews: (id: string) => void;
18
19
  unselectAllViews: () => void;
19
20
  };
20
21
  export {};
@@ -1,4 +1,8 @@
1
- import { reactive, computed, toValue } from "vue";
1
+ import {
2
+ reactive,
3
+ computed,
4
+ toValue
5
+ } from "vue";
2
6
  import { useMenuState } from "./useMenuState.mjs";
3
7
  export function useMenuView(instanceId) {
4
8
  const { initializeState } = useMenuState(instanceId);
@@ -16,10 +20,6 @@ export function useMenuView(instanceId) {
16
20
  const view = {
17
21
  id,
18
22
  parent,
19
- children: {
20
- trigger: void 0,
21
- content: void 0
22
- },
23
23
  active: false,
24
24
  items: [],
25
25
  channels: [],
@@ -69,17 +69,23 @@ export function useMenuView(instanceId) {
69
69
  const parentId = view?.parent.views[view.parent.views.length - 1];
70
70
  return getView(parentId ?? "");
71
71
  }
72
- function getNonTreeViews(id) {
73
- const currentView2 = getView(id);
72
+ function getUnrelatedViews(id) {
73
+ const argView = getView(id);
74
74
  return state.views?.filter(
75
- (view) => !currentView2?.parent.views.includes(view.id) && view.id !== id
75
+ (view) => !view.parent.views.includes(id) && !argView?.parent.views.includes(view.id) && view.id !== id
76
+ );
77
+ }
78
+ function getDescendingViews(id) {
79
+ const argView = getView(id);
80
+ return state.views?.filter(
81
+ (view) => view.id !== id && !argView?.parent.views.includes(view.id)
76
82
  );
77
83
  }
78
84
  function selectView(id) {
79
85
  const instance = getView(id);
80
86
  if (instance) {
81
87
  instance.active = true;
82
- unselectNonTreeViews(id);
88
+ unselectUnrelatedViews(id);
83
89
  }
84
90
  }
85
91
  function unselectView(id) {
@@ -88,9 +94,13 @@ export function useMenuView(instanceId) {
88
94
  instance.active = false;
89
95
  }
90
96
  }
91
- function unselectNonTreeViews(id) {
92
- const nonTreeViews = getNonTreeViews(id);
93
- nonTreeViews.forEach((view) => view.active = false);
97
+ function unselectUnrelatedViews(id) {
98
+ const unrelatedViews = getUnrelatedViews(id);
99
+ unrelatedViews.forEach((view) => view.active = false);
100
+ }
101
+ function unselectDescendingViews(id) {
102
+ const descendingViews = getDescendingViews(id);
103
+ descendingViews.forEach((view) => view.active = false);
94
104
  }
95
105
  function unselectAllViews() {
96
106
  state.views?.forEach((view) => {
@@ -110,7 +120,8 @@ export function useMenuView(instanceId) {
110
120
  getParentView,
111
121
  selectView,
112
122
  unselectView,
113
- unselectNonTreeViews,
123
+ unselectUnrelatedViews,
124
+ unselectDescendingViews,
114
125
  unselectAllViews
115
126
  };
116
127
  }
@@ -1,7 +1,7 @@
1
1
  import { type MaybeRef } from 'vue';
2
2
  interface UseMagicMenuArgs {
3
3
  instanceId: MaybeRef<string>;
4
- viewId?: string;
4
+ viewId: string;
5
5
  }
6
6
  export declare function useMagicMenu(args: UseMagicMenuArgs): {
7
7
  selectView: (id: string) => void;
@@ -5,7 +5,7 @@ export function useMagicMenu(args) {
5
5
  const { selectView, unselectView } = useMenuView(instanceId);
6
6
  const { selectChannel, unselectChannel } = useMenuChannel({
7
7
  instanceId,
8
- viewId: viewId ?? ""
8
+ viewId
9
9
  });
10
10
  return {
11
11
  selectView,
@@ -9,7 +9,7 @@ export interface MagicMenuOptions {
9
9
  nested?: string;
10
10
  };
11
11
  }
12
- export type Interaction = 'click' | 'mouseenter' | 'mouseleave' | 'right-click';
12
+ export type Interaction = 'click' | 'mouseenter' | 'right-click';
13
13
  export type Coordinates = {
14
14
  x: number;
15
15
  y: number;
@@ -32,10 +32,6 @@ export interface MenuView {
32
32
  item: string;
33
33
  views: string[];
34
34
  };
35
- children: {
36
- trigger?: HTMLElement;
37
- content?: HTMLElement;
38
- };
39
35
  placement: Placement;
40
36
  click?: Coordinates;
41
37
  }
@@ -1,9 +1,9 @@
1
1
  const defaultOptions = {
2
2
  mode: "menubar",
3
3
  transition: {
4
- initial: "magic-menu-content__initial",
5
- final: "magic-menu-content__final",
6
- nested: "magic-menu-content__nested"
4
+ initial: "magic-menu-content--initial",
5
+ final: "magic-menu-content--final",
6
+ nested: "magic-menu-content--nested"
7
7
  }
8
8
  };
9
9
  export { defaultOptions };
@@ -24,7 +24,7 @@ declare const _default: __VLS_WithTemplateSlots<import("vue").DefineComponent<__
24
24
  loop: boolean;
25
25
  srcType: MagicPlayerSourceType;
26
26
  autoplay: boolean;
27
- preload: "metadata" | "auto" | "none";
27
+ preload: "metadata" | "none" | "auto";
28
28
  }, {}>, {
29
29
  default?(_: {}): any;
30
30
  }>;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@maas/vue-equipment",
3
3
  "description": "A magic collection of Vue composables, plugins, components and directives",
4
- "version": "0.30.1",
4
+ "version": "0.30.3",
5
5
  "author": "Robin Scholz <https://github.com/robinscholz>, Christoph Jeworutzki <https://github.com/ChristophJeworutzki>",
6
6
  "devDependencies": {
7
7
  "@antfu/ni": "^0.21.12",