@maas/vue-equipment 0.30.0 → 0.30.2

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 (38) hide show
  1. package/dist/nuxt/module.json +1 -1
  2. package/dist/nuxt/module.mjs +2 -2
  3. package/dist/plugins/MagicAutoSize/src/components/MagicAutoSize.vue +13 -8
  4. package/dist/plugins/MagicMenu/index.mjs +4 -0
  5. package/dist/plugins/MagicMenu/src/components/MagicMenuChannel.vue +69 -0
  6. package/dist/plugins/MagicMenu/src/components/MagicMenuChannel.vue.d.ts +21 -0
  7. package/dist/plugins/MagicMenu/src/components/MagicMenuContent.vue +61 -16
  8. package/dist/plugins/MagicMenu/src/components/MagicMenuContent.vue.d.ts +0 -2
  9. package/dist/plugins/MagicMenu/src/components/MagicMenuFloat.vue +2 -2
  10. package/dist/plugins/MagicMenu/src/components/MagicMenuItem.vue +1 -1
  11. package/dist/plugins/MagicMenu/src/components/MagicMenuProvider.vue +1 -5
  12. package/dist/plugins/MagicMenu/src/components/MagicMenuRemote.vue +84 -0
  13. package/dist/plugins/MagicMenu/src/components/MagicMenuRemote.vue.d.ts +29 -0
  14. package/dist/plugins/MagicMenu/src/components/MagicMenuTrigger.vue +25 -31
  15. package/dist/plugins/MagicMenu/src/components/MagicMenuView.vue +26 -0
  16. package/dist/plugins/MagicMenu/src/components/MagicMenuView.vue.d.ts +2 -0
  17. package/dist/plugins/MagicMenu/src/composables/private/useMenuCallback.d.ts +2 -1
  18. package/dist/plugins/MagicMenu/src/composables/private/useMenuCallback.mjs +6 -1
  19. package/dist/plugins/MagicMenu/src/composables/private/useMenuChannel.d.ts +15 -0
  20. package/dist/plugins/MagicMenu/src/composables/private/useMenuChannel.mjs +70 -0
  21. package/dist/plugins/MagicMenu/src/composables/private/useMenuCursor.d.ts +21 -0
  22. package/dist/plugins/MagicMenu/src/composables/private/useMenuCursor.mjs +212 -0
  23. package/dist/plugins/MagicMenu/src/composables/private/useMenuItem.mjs +2 -2
  24. package/dist/plugins/MagicMenu/src/composables/private/useMenuKeyListener.mjs +4 -4
  25. package/dist/plugins/MagicMenu/src/composables/private/useMenuRemote.d.ts +13 -0
  26. package/dist/plugins/MagicMenu/src/composables/private/useMenuRemote.mjs +23 -0
  27. package/dist/plugins/MagicMenu/src/composables/private/useMenuTrigger.d.ts +1 -3
  28. package/dist/plugins/MagicMenu/src/composables/private/useMenuTrigger.mjs +2 -180
  29. package/dist/plugins/MagicMenu/src/composables/private/useMenuView.d.ts +4 -3
  30. package/dist/plugins/MagicMenu/src/composables/private/useMenuView.mjs +28 -15
  31. package/dist/plugins/MagicMenu/src/composables/useMagicMenu.d.ts +8 -1
  32. package/dist/plugins/MagicMenu/src/composables/useMagicMenu.mjs +10 -2
  33. package/dist/plugins/MagicMenu/src/symbols/index.d.ts +3 -1
  34. package/dist/plugins/MagicMenu/src/symbols/index.mjs +5 -1
  35. package/dist/plugins/MagicMenu/src/types/index.d.ts +9 -6
  36. package/dist/plugins/MagicMenu/src/utils/defaultOptions.mjs +3 -3
  37. package/dist/plugins/MagicPlayer/src/components/MagicPlayer.vue.d.ts +1 -1
  38. 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.29.8",
4
+ "version": "0.30.1",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "0.8.0",
7
7
  "unbuild": "unknown"
@@ -4,7 +4,7 @@ const functions$1 = [
4
4
  {
5
5
  name: "MagicAutoSize",
6
6
  "package": "plugins",
7
- lastUpdated: 0,
7
+ lastUpdated: 1719323171000,
8
8
  docs: "https://maas.egineering/vue-equipment/plugins/MagicAutoSize/",
9
9
  description: "auto Size"
10
10
  },
@@ -53,7 +53,7 @@ const functions$1 = [
53
53
  {
54
54
  name: "MagicMenu",
55
55
  "package": "plugins",
56
- lastUpdated: 1719315337000,
56
+ lastUpdated: 1719385203000,
57
57
  docs: "https://maas.egineering/vue-equipment/plugins/MagicMenu/",
58
58
  description: "menu"
59
59
  },
@@ -55,12 +55,13 @@ const mappedSize = computed(() => {
55
55
  useMutationObserver(
56
56
  elRef,
57
57
  (mutations) => {
58
- const addedNodes: HTMLElement[] = mutations
58
+ const filtered = mutations
59
59
  .flatMap((m) => [...m.addedNodes])
60
- .filter((n) => n instanceof HTMLElement)
61
- .map((n) => n as HTMLElement)
60
+ .find((n) => n instanceof HTMLElement)
62
61
 
63
- content.value = addedNodes[0]
62
+ if (!!filtered && filtered instanceof HTMLElement) {
63
+ content.value = filtered
64
+ }
64
65
  },
65
66
  {
66
67
  childList: true,
@@ -78,11 +79,15 @@ useResizeObserver(content, () => {
78
79
 
79
80
  onMounted(() => {
80
81
  if (elRef.value) {
81
- const content = elRef.value.querySelector('*')
82
- if (content instanceof HTMLElement) {
82
+ const content = elRef.value.querySelectorAll('*')
83
+ const filtered = Array.from(content).find(
84
+ (node) => node instanceof HTMLElement
85
+ )
86
+
87
+ if (!!filtered && filtered instanceof HTMLElement) {
83
88
  size.value = {
84
- width: content.offsetWidth,
85
- height: content.offsetHeight,
89
+ width: filtered.offsetWidth,
90
+ height: filtered.offsetHeight,
86
91
  }
87
92
  }
88
93
  }
@@ -1,7 +1,9 @@
1
1
  import MagicMenuFloat from "./src/components/MagicMenuFloat.vue";
2
2
  import MagicMenuContent from "./src/components/MagicMenuContent.vue";
3
+ import MagicMenuChannel from "./src/components/MagicMenuChannel.vue";
3
4
  import MagicMenuItem from "./src/components/MagicMenuItem.vue";
4
5
  import MagicMenuProvider from "./src/components/MagicMenuProvider.vue";
6
+ import MagicMenuRemote from "./src/components/MagicMenuRemote.vue";
5
7
  import MagicMenuTrigger from "./src/components/MagicMenuTrigger.vue";
6
8
  import MagicMenuView from "./src/components/MagicMenuView.vue";
7
9
  import { useMagicMenu } from "./src/composables/useMagicMenu.mjs";
@@ -14,8 +16,10 @@ const MagicMenuPlugin = {
14
16
  install: (app) => {
15
17
  app.component("MagicMenuFloat", MagicMenuFloat);
16
18
  app.component("MagicMenuContent", MagicMenuContent);
19
+ app.component("MagicMenuChannel", MagicMenuChannel);
17
20
  app.component("MagicMenuItem", MagicMenuItem);
18
21
  app.component("MagicMenuProvider", MagicMenuProvider);
22
+ app.component("MagicMenuRemote", MagicMenuRemote);
19
23
  app.component("MagicMenuTrigger", MagicMenuTrigger);
20
24
  app.component("MagicMenuView", MagicMenuView);
21
25
  }
@@ -0,0 +1,69 @@
1
+ <template>
2
+ <div
3
+ class="magic-menu-channel"
4
+ v-if="channel.active"
5
+ :data-id="mappedId"
6
+ :id="id"
7
+ >
8
+ <slot />
9
+ </div>
10
+ </template>
11
+
12
+ <script lang="ts" setup>
13
+ import { computed, inject, provide, onBeforeUnmount } from 'vue'
14
+ import { useMenuChannel } from '../composables/private/useMenuChannel'
15
+ import {
16
+ MagicMenuInstanceId,
17
+ MagicMenuViewId,
18
+ MagicMenuContentId,
19
+ MagicMenuChannelId,
20
+ MagicMenuChannelActive,
21
+ } from '../symbols'
22
+
23
+ interface MagicMenuChannelProps {
24
+ id: string
25
+ }
26
+
27
+ const props = defineProps<MagicMenuChannelProps>()
28
+
29
+ const instanceId = inject(MagicMenuInstanceId, undefined)
30
+ const viewId = inject(MagicMenuViewId, undefined)
31
+ const contentId = inject(MagicMenuContentId, undefined)
32
+
33
+ if (!instanceId) {
34
+ throw new Error('MagicMenuChannel must be nested inside MagicMenuProvider')
35
+ }
36
+
37
+ if (!viewId) {
38
+ throw new Error('MagicMenuChannel must be nested inside MagicMenuView')
39
+ }
40
+
41
+ if (!contentId) {
42
+ throw new Error('MagicMenuChannel must be nested inside MagicMenuContent')
43
+ }
44
+
45
+ if (!props.id) {
46
+ throw new Error('MagicMenuChannel requires an id')
47
+ }
48
+
49
+ const mappedId = computed(() => `magic-menu-channel-${props.id}`)
50
+
51
+ // Register channel
52
+ const { initializeChannel, deleteChannel } = useMenuChannel({
53
+ instanceId,
54
+ viewId,
55
+ })
56
+
57
+ const channel = initializeChannel({
58
+ id: mappedId.value,
59
+ })
60
+
61
+ // Pass id and active state to children
62
+ provide(MagicMenuChannelId, mappedId.value)
63
+ provide(MagicMenuChannelActive, channel.active)
64
+
65
+ // Lifecycle
66
+ onBeforeUnmount(() => {
67
+ deleteChannel(mappedId.value)
68
+ })
69
+ </script>
@@ -0,0 +1,21 @@
1
+ interface MagicMenuChannelProps {
2
+ id: string;
3
+ }
4
+ declare const _default: __VLS_WithTemplateSlots<import("vue").DefineComponent<__VLS_TypePropsToOption<MagicMenuChannelProps>, {}, unknown, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<__VLS_TypePropsToOption<MagicMenuChannelProps>>>, {}, {}>, {
5
+ default?(_: {}): any;
6
+ }>;
7
+ export default _default;
8
+ type __VLS_WithTemplateSlots<T, S> = T & {
9
+ new (): {
10
+ $slots: S;
11
+ };
12
+ };
13
+ type __VLS_NonUndefinedable<T> = T extends undefined ? never : T;
14
+ type __VLS_TypePropsToOption<T> = {
15
+ [K in keyof T]-?: {} extends Pick<T, K> ? {
16
+ type: import('vue').PropType<__VLS_NonUndefinedable<T[K]>>;
17
+ } : {
18
+ type: import('vue').PropType<T[K]>;
19
+ required: true;
20
+ };
21
+ };
@@ -16,7 +16,7 @@
16
16
  v-if="innerActive"
17
17
  >
18
18
  <magic-menu-float
19
- :placement="placement"
19
+ :placement="view?.placement"
20
20
  :arrow="arrow"
21
21
  :reference-el="referenceEl"
22
22
  >
@@ -44,9 +44,13 @@ import {
44
44
  computed,
45
45
  type MaybeRef,
46
46
  type ComponentPublicInstance,
47
+ onBeforeUnmount,
47
48
  } from 'vue'
48
- import type { Placement } from '@floating-ui/vue'
49
49
  import { useMenuView } from '../composables/private/useMenuView'
50
+ import { useMenuState } from '../composables/private/useMenuState'
51
+ import { useMenuCallback } from '../composables/private/useMenuCallback'
52
+ import { useMenuDOM } from '../composables/private/useMenuDOM'
53
+ import { useMenuCursor } from '../composables/private/useMenuCursor'
50
54
  import {
51
55
  MagicMenuInstanceId,
52
56
  MagicMenuViewId,
@@ -55,16 +59,12 @@ import {
55
59
 
56
60
  import '@maas/vue-equipment/utils/css/animations/fade-in.css'
57
61
  import '@maas/vue-equipment/utils/css/animations/fade-out.css'
58
- import { useMenuState } from '../composables/private/useMenuState'
59
- import { useMenuCallback } from '../composables/private/useMenuCallback'
60
- import { useMenuDOM } from '../composables/private/useMenuDOM'
61
62
 
62
63
  defineOptions({
63
64
  inheritAttrs: false,
64
65
  })
65
66
 
66
67
  interface MagicMenuContentProps {
67
- placement?: Placement
68
68
  arrow?: boolean | undefined
69
69
  referenceEl?: MaybeRef<HTMLElement | ComponentPublicInstance>
70
70
  }
@@ -103,6 +103,10 @@ const mappedTransition = computed(() => {
103
103
  }
104
104
  })
105
105
 
106
+ // Split isActive into two values to animate content smoothly
107
+ const innerActive = ref(false)
108
+ const wrapperActive = ref(false)
109
+
106
110
  const { lockScroll, unlockScroll } = useMenuDOM()
107
111
  const {
108
112
  onBeforeEnter,
@@ -117,11 +121,22 @@ const {
117
121
  viewId,
118
122
  lockScroll,
119
123
  unlockScroll,
124
+ wrapperActive,
120
125
  })
121
126
 
122
- // Split isActive into two values to animate content smoothly
123
- const innerActive = ref(false)
124
- const wrapperActive = ref(false)
127
+ // Handle cursor
128
+ const mappedClick = computed(() => view?.click)
129
+ const mappedPlacement = computed(() => view?.placement ?? 'bottom')
130
+ const mappedTrigger = computed(
131
+ () => document.querySelector(`[data-id="${viewId}-trigger"]`) as HTMLElement
132
+ )
133
+
134
+ const { destroy, initialize, isInsideTriangle, isInsideTo } = useMenuCursor({
135
+ from: mappedTrigger,
136
+ to: contentRef,
137
+ placement: mappedPlacement,
138
+ click: mappedClick,
139
+ })
125
140
 
126
141
  // Handle state
127
142
  async function onOpen() {
@@ -129,15 +144,20 @@ async function onOpen() {
129
144
  await nextTick()
130
145
  innerActive.value = true
131
146
  await nextTick()
132
- if (view) {
133
- view.children.content = contentRef.value
134
- }
147
+ initialize()
135
148
  }
136
149
 
137
- async function onClose() {
150
+ function onClose() {
151
+ destroy()
138
152
  innerActive.value = false
139
- await nextTick()
140
- wrapperActive.value = false
153
+ }
154
+
155
+ function disableCursor() {
156
+ state.input.disabled = [...state.input.disabled, 'pointer']
157
+ }
158
+
159
+ function enableCursor() {
160
+ state.input.disabled = state.input.disabled.filter((x) => x !== 'pointer')
141
161
  }
142
162
 
143
163
  watch(
@@ -151,9 +171,34 @@ watch(
151
171
  }
152
172
  )
153
173
 
174
+ watch(isInsideTriangle, (value) => {
175
+ if (value) {
176
+ disableCursor()
177
+ } else {
178
+ enableCursor()
179
+ }
180
+ })
181
+
182
+ watch(isInsideTo, (value) => {
183
+ if (value) {
184
+ enableCursor()
185
+ } else {
186
+ switch (state.options.mode) {
187
+ case 'navigation':
188
+ if (!isInsideTriangle.value) {
189
+ view!.active = false
190
+ }
191
+ }
192
+ }
193
+ })
194
+
195
+ onBeforeUnmount(async () => {
196
+ destroy()
197
+ })
198
+
154
199
  provide(MagicMenuContentId, `${viewId}-content`)
155
200
  </script>
156
201
 
157
202
  <style>
158
- .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}
203
+ .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}
159
204
  </style>
@@ -1,9 +1,7 @@
1
1
  import { type MaybeRef, type ComponentPublicInstance } from 'vue';
2
- import type { Placement } from '@floating-ui/vue';
3
2
  import '@maas/vue-equipment/utils/css/animations/fade-in.css';
4
3
  import '@maas/vue-equipment/utils/css/animations/fade-out.css';
5
4
  interface MagicMenuContentProps {
6
- placement?: Placement;
7
5
  arrow?: boolean | undefined;
8
6
  referenceEl?: MaybeRef<HTMLElement | ComponentPublicInstance>;
9
7
  }
@@ -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>
@@ -93,7 +93,7 @@ function guardedUnselect() {
93
93
  unselectItem(mappedId.value)
94
94
  } else {
95
95
  // If there is a nested active view,
96
- // unselect the item once it is closed
96
+ // unselect the item once it is closed
97
97
  watch(
98
98
  () => nestedView.value?.active,
99
99
  (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
 
@@ -0,0 +1,84 @@
1
+ <template>
2
+ <div
3
+ class="magic-menu-remote"
4
+ :class="{ '-active': channel?.active, '-disabled': disabled }"
5
+ :data-id="`${channelId}-remote`"
6
+ @click="onClick"
7
+ @mouseenter="onMouseenter"
8
+ >
9
+ <slot :is-active="view?.active" :is-disabled="disabled" />
10
+ </div>
11
+ </template>
12
+
13
+ <script lang="ts" setup>
14
+ import { computed, inject, watch } from 'vue'
15
+ import { useMenuView } from '../composables/private/useMenuView'
16
+ import { useMenuChannel } from '../composables/private/useMenuChannel'
17
+ import { MagicMenuInstanceId, MagicMenuViewId } from '../symbols'
18
+
19
+ import type { Interaction } from '../types'
20
+ import { useMenuRemote } from '../composables/private/useMenuRemote'
21
+
22
+ interface MagicMenuRemoteProps {
23
+ channelId: string
24
+ viewId?: string
25
+ instanceId?: string
26
+ disabled?: boolean
27
+ trigger?: Interaction[]
28
+ }
29
+
30
+ const props = defineProps<MagicMenuRemoteProps>()
31
+
32
+ const instanceId = inject(MagicMenuInstanceId, props.instanceId)
33
+ const viewId = inject(MagicMenuViewId, props.viewId)
34
+
35
+ if (!instanceId) {
36
+ throw new Error(
37
+ 'MagicMenuRemote must be nested inside MagicMenuProvider or an instanceId must be provided'
38
+ )
39
+ }
40
+
41
+ if (!viewId) {
42
+ throw new Error(
43
+ 'MagicMenuTrigger must be nested inside MagicMenuView or a viewId must be provided'
44
+ )
45
+ }
46
+
47
+ if (!props.channelId) {
48
+ throw new Error('MagicMenuRemote requires a channelId')
49
+ }
50
+
51
+ const mappedChannelId = computed(() => `magic-menu-channel-${props.channelId}`)
52
+ const mappedTrigger = computed<Interaction[]>(
53
+ () => props.trigger ?? ['mouseenter']
54
+ )
55
+
56
+ const { getView } = useMenuView(instanceId)
57
+ const view = getView(viewId)
58
+
59
+ const { initializeChannel, deleteChannel } = useMenuChannel({
60
+ instanceId,
61
+ viewId,
62
+ })
63
+ let channel = initializeChannel({ id: mappedChannelId.value })
64
+
65
+ const { onClick, onMouseenter } = useMenuRemote({
66
+ viewId,
67
+ instanceId,
68
+ mappedChannelId,
69
+ mappedTrigger,
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
+ )
80
+ </script>
81
+
82
+ <style>
83
+ .magic-menu-remote{cursor:var(--magic-menu-remote-cursor,pointer)}
84
+ </style>
@@ -0,0 +1,29 @@
1
+ import type { Interaction } from '../types';
2
+ interface MagicMenuRemoteProps {
3
+ channelId: string;
4
+ viewId?: string;
5
+ instanceId?: string;
6
+ disabled?: boolean;
7
+ trigger?: Interaction[];
8
+ }
9
+ declare const _default: __VLS_WithTemplateSlots<import("vue").DefineComponent<__VLS_TypePropsToOption<MagicMenuRemoteProps>, {}, unknown, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<__VLS_TypePropsToOption<MagicMenuRemoteProps>>>, {}, {}>, {
10
+ default?(_: {
11
+ isActive: boolean | undefined;
12
+ isDisabled: boolean | undefined;
13
+ }): any;
14
+ }>;
15
+ export default _default;
16
+ type __VLS_WithTemplateSlots<T, S> = T & {
17
+ new (): {
18
+ $slots: S;
19
+ };
20
+ };
21
+ type __VLS_NonUndefinedable<T> = T extends undefined ? never : T;
22
+ type __VLS_TypePropsToOption<T> = {
23
+ [K in keyof T]-?: {} extends Pick<T, K> ? {
24
+ type: import('vue').PropType<__VLS_NonUndefinedable<T[K]>>;
25
+ } : {
26
+ type: import('vue').PropType<T[K]>;
27
+ required: true;
28
+ };
29
+ };
@@ -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,16 +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']
72
- : ['mouseenter', 'click']
73
- case 'dropdown':
74
- return view?.parent.item
75
- ? ['mouseenter', 'mouseleave', 'click']
71
+ ? ['mouseenter', 'click']
72
+ : state.active
73
+ ? ['mouseenter', 'click']
76
74
  : ['click']
75
+ case 'dropdown':
76
+ return view?.parent.item ? ['mouseenter', 'click'] : ['click']
77
77
  case 'context':
78
- return view?.parent.item
79
- ? ['mouseenter', 'mouseleave', 'click']
80
- : ['right-click']
78
+ return view?.parent.item ? ['mouseenter', 'click'] : ['right-click']
79
+ case 'navigation':
80
+ return ['mouseenter']
81
81
  }
82
82
  })
83
83
 
@@ -89,32 +89,26 @@ const mappedTabindex = computed(() => {
89
89
  }
90
90
  })
91
91
 
92
- const { initialize, destroy, onMouseenter, onClick, onMouseleave } =
93
- useMenuTrigger({
94
- instanceId,
95
- viewId,
96
- itemId,
97
- mappedDisabled,
98
- mappedTrigger,
99
- elRef,
100
- })
101
-
102
- // Initialize watcher
103
- initialize()
92
+ const { onMouseenter, onClick, onEnter } = useMenuTrigger({
93
+ instanceId,
94
+ viewId,
95
+ itemId,
96
+ mappedDisabled,
97
+ mappedTrigger,
98
+ elRef,
99
+ })
104
100
 
105
101
  watch(
106
- elRef,
107
- (value) => {
108
- if (view && value) {
109
- view.children.trigger = value
102
+ () => view?.active,
103
+ async (value) => {
104
+ if (value) {
105
+ await new Promise((resolve) => requestAnimationFrame(resolve))
106
+ toValue(elRef)?.blur()
110
107
  }
111
- },
112
- { immediate: true }
108
+ }
113
109
  )
114
110
 
115
- onBeforeUnmount(() => {
116
- destroy()
117
- })
111
+ onKeyStroke('Enter', onEnter)
118
112
  </script>
119
113
 
120
114
  <style>
@@ -16,9 +16,12 @@ import {
16
16
  MagicMenuItemId,
17
17
  MagicMenuViewActive,
18
18
  } from '../symbols'
19
+ import type { Placement } from '@floating-ui/vue'
20
+ import { useMenuState } from '../composables/private/useMenuState'
19
21
 
20
22
  interface MagicMenuViewProps {
21
23
  id?: string
24
+ placement?: Placement
22
25
  }
23
26
 
24
27
  const props = defineProps<MagicMenuViewProps>()
@@ -36,9 +39,32 @@ const mappedParentTree = computed(() => [...parentTree, mappedId.value])
36
39
 
37
40
  // Register view
38
41
  const { initializeView, deleteView } = useMenuView(instanceId)
42
+ const { initializeState } = useMenuState(instanceId)
43
+ const state = initializeState()
44
+
45
+ const mappedPlacement = computed(() => {
46
+ if (props.placement) {
47
+ return props.placement
48
+ }
49
+
50
+ switch (state.options.mode) {
51
+ case 'navigation':
52
+ return 'bottom'
53
+ case 'menubar':
54
+ return !itemId ? 'bottom-start' : 'right-start'
55
+ case 'dropdown':
56
+ return !itemId ? 'bottom' : 'right-start'
57
+ case 'context':
58
+ return 'right-start'
59
+ default:
60
+ return 'bottom'
61
+ }
62
+ })
63
+
39
64
  const view = initializeView({
40
65
  id: mappedId.value,
41
66
  parent: { views: parentTree, item: itemId ?? '' },
67
+ placement: mappedPlacement.value,
42
68
  })
43
69
 
44
70
  // Pass id, active state and parent tree to children
@@ -1,5 +1,7 @@
1
+ import type { Placement } from '@floating-ui/vue';
1
2
  interface MagicMenuViewProps {
2
3
  id?: string;
4
+ placement?: Placement;
3
5
  }
4
6
  declare const _default: __VLS_WithTemplateSlots<import("vue").DefineComponent<__VLS_TypePropsToOption<MagicMenuViewProps>, {}, unknown, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<__VLS_TypePropsToOption<MagicMenuViewProps>>>, {}, {}>, {
5
7
  default?(_: {}): any;
@@ -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;