@xen-orchestra/web-core 0.6.0 → 0.8.0
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.
- package/lib/assets/css/typography/_style.pcss +1 -0
- package/lib/assets/not-found.svg +129 -0
- package/lib/components/card/VtsCardRowKeyValue.vue +48 -0
- package/lib/components/layout/VtsLayoutSidebar.vue +1 -1
- package/lib/components/state-hero/VtsErrorNoDataHero.vue +11 -0
- package/lib/components/state-hero/VtsNoSelectionHero.vue +13 -0
- package/lib/components/state-hero/VtsObjectNotFoundHero.vue +3 -2
- package/lib/components/state-hero/VtsPageNotFoundHero.vue +30 -0
- package/lib/components/state-hero/VtsStateHero.vue +31 -3
- package/lib/components/ui/donut-chart/UiDonutChart.vue +2 -2
- package/lib/components/ui/dropdown-button/UiDropdownButton.vue +81 -0
- package/lib/components/ui/link/UiLink.vue +75 -0
- package/lib/components/ui/tag/UiTagsList.vue +14 -0
- package/lib/composables/link-component.composable.ts +53 -0
- package/lib/locales/cs.json +1 -0
- package/lib/locales/de.json +3 -0
- package/lib/locales/en.json +5 -0
- package/lib/locales/fa.json +3 -0
- package/lib/locales/fr.json +5 -0
- package/lib/packages/job/README.md +130 -0
- package/lib/packages/job/define-job-arg.ts +12 -0
- package/lib/packages/job/define-job.ts +130 -0
- package/lib/packages/job/index.ts +4 -0
- package/lib/packages/job/job-error.ts +14 -0
- package/lib/packages/job/use-job-store.ts +44 -0
- package/lib/packages/menu/README.md +194 -0
- package/lib/packages/menu/action.ts +101 -0
- package/lib/packages/menu/base.ts +26 -0
- package/lib/packages/menu/context.ts +27 -0
- package/lib/packages/menu/index.ts +10 -0
- package/lib/packages/menu/job.ts +15 -0
- package/lib/packages/menu/link.ts +56 -0
- package/lib/packages/menu/menu.ts +50 -0
- package/lib/packages/menu/router-link.ts +51 -0
- package/lib/packages/menu/structure.ts +88 -0
- package/lib/packages/menu/toggle-target.ts +59 -0
- package/lib/packages/menu/toggle-trigger.ts +72 -0
- package/lib/packages/menu/toggle.ts +43 -0
- package/package.json +2 -1
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { MenuAction, MenuLink, MenuRouterLink, MenuToggleTrigger, Menu } from '@core/packages/menu'
|
|
2
|
+
import { computed } from 'vue'
|
|
3
|
+
|
|
4
|
+
export type MenuItem = MenuAction | MenuLink | MenuRouterLink | MenuToggleTrigger
|
|
5
|
+
|
|
6
|
+
export abstract class BaseItem {
|
|
7
|
+
id = Symbol('Menu Item')
|
|
8
|
+
|
|
9
|
+
protected constructor(public menu: Menu) {
|
|
10
|
+
menu.addItem(this as any)
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
get isActive() {
|
|
14
|
+
return computed(() => this.menu.context.activeItemId.value === this.id)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
activate() {
|
|
18
|
+
this.menu.context.activeItemId.value = this.id
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
deactivate() {
|
|
22
|
+
if (this.isActive.value) {
|
|
23
|
+
this.menu.context.activeItemId.value = undefined
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { uniqueId } from '@core/utils/unique-id.util'
|
|
2
|
+
import { useEventListener } from '@vueuse/core'
|
|
3
|
+
import { type Ref, ref } from 'vue'
|
|
4
|
+
|
|
5
|
+
export class MenuContext {
|
|
6
|
+
id: string
|
|
7
|
+
|
|
8
|
+
activeItemId: Ref<symbol | undefined> = ref()
|
|
9
|
+
|
|
10
|
+
constructor() {
|
|
11
|
+
this.id = uniqueId()
|
|
12
|
+
|
|
13
|
+
useEventListener(window, 'click', (event: PointerEvent) => {
|
|
14
|
+
if (this.hasSameController(event)) {
|
|
15
|
+
return
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
this.activeItemId.value = undefined
|
|
19
|
+
})
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
hasSameController(event: PointerEvent) {
|
|
23
|
+
return Array.from(window.document.querySelectorAll(`[data-menu-id="${this.id}"]`)).some(
|
|
24
|
+
el => el === event.target || event.composedPath().includes(el)
|
|
25
|
+
)
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export * from './base'
|
|
2
|
+
export * from './action'
|
|
3
|
+
export * from './context'
|
|
4
|
+
export * from './link'
|
|
5
|
+
export * from './menu'
|
|
6
|
+
export * from './router-link'
|
|
7
|
+
export * from './structure'
|
|
8
|
+
export * from './toggle'
|
|
9
|
+
export * from './toggle-target'
|
|
10
|
+
export * from './toggle-trigger'
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Job } from '@core/packages/job'
|
|
2
|
+
import { action } from '@core/packages/menu/action'
|
|
3
|
+
import type { MenuLike } from '@core/packages/menu/menu'
|
|
4
|
+
import { parseConfigHolder } from '@core/packages/menu/structure'
|
|
5
|
+
|
|
6
|
+
export function job(job: Job<any>) {
|
|
7
|
+
return action(() => job.run(), {
|
|
8
|
+
busy: job.isRunning,
|
|
9
|
+
disabled: job.errorMessage,
|
|
10
|
+
})
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function useMenuJob(config: { job: Job<any>; parent: MenuLike }) {
|
|
14
|
+
return parseConfigHolder(config.parent, job(config.job))
|
|
15
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { BaseItem, type Menu, type MenuLike, parseConfigHolder } from '@core/packages/menu'
|
|
2
|
+
import { computed, type MaybeRefOrGetter, reactive, toValue } from 'vue'
|
|
3
|
+
|
|
4
|
+
export interface MenuLinkConfig {
|
|
5
|
+
href: MaybeRefOrGetter<string>
|
|
6
|
+
rel?: MaybeRefOrGetter<string>
|
|
7
|
+
target?: MaybeRefOrGetter<string>
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export class MenuLinkConfigHolder {
|
|
11
|
+
constructor(public config: MenuLinkConfig) {}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface MenuLinkProps {
|
|
15
|
+
as: 'a'
|
|
16
|
+
href: string
|
|
17
|
+
rel: string
|
|
18
|
+
target: string
|
|
19
|
+
onClick: () => void
|
|
20
|
+
onMouseenter: () => void
|
|
21
|
+
'data-menu-id': string
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export class MenuLink extends BaseItem {
|
|
25
|
+
constructor(
|
|
26
|
+
public menu: Menu,
|
|
27
|
+
public config: MenuLinkConfig
|
|
28
|
+
) {
|
|
29
|
+
super(menu)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
get props(): MenuLinkProps {
|
|
33
|
+
return reactive({
|
|
34
|
+
as: 'a',
|
|
35
|
+
onMouseenter: () => this.activate(),
|
|
36
|
+
onClick: () => this.deactivate(),
|
|
37
|
+
href: computed(() => toValue(this.config.href)),
|
|
38
|
+
rel: computed(() => toValue(this.config.rel) ?? 'noreferrer noopener'),
|
|
39
|
+
target: computed(() => toValue(this.config.target) ?? '_blank'),
|
|
40
|
+
'data-menu-id': this.menu.context.id,
|
|
41
|
+
})
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function link(href: MaybeRefOrGetter<string>, config: Omit<MenuLinkConfig, 'href'> = {}) {
|
|
46
|
+
return new MenuLinkConfigHolder({
|
|
47
|
+
...config,
|
|
48
|
+
href,
|
|
49
|
+
})
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function useMenuLink(config: MenuLinkConfig & { parent: MenuLike }) {
|
|
53
|
+
const { parent, href, ...configRest } = config
|
|
54
|
+
|
|
55
|
+
return parseConfigHolder(parent, link(href, configRest))
|
|
56
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import {
|
|
2
|
+
MenuAction,
|
|
3
|
+
MenuContext,
|
|
4
|
+
MenuLink,
|
|
5
|
+
MenuRouterLink,
|
|
6
|
+
type MenuStructure,
|
|
7
|
+
MenuToggleTrigger,
|
|
8
|
+
type ParseStructure,
|
|
9
|
+
parseStructure,
|
|
10
|
+
} from '@core/packages/menu'
|
|
11
|
+
import { extendRef } from '@vueuse/core'
|
|
12
|
+
import { computed, type ComputedRef, type MaybeRefOrGetter, shallowReactive, type ShallowReactive } from 'vue'
|
|
13
|
+
|
|
14
|
+
export const MENU_SYMBOL = Symbol('Submenu')
|
|
15
|
+
|
|
16
|
+
export type WithMenu = { [MENU_SYMBOL]: Menu }
|
|
17
|
+
|
|
18
|
+
export type MenuLike = Menu | WithMenu
|
|
19
|
+
|
|
20
|
+
type MenuItem = MenuAction | MenuLink | MenuRouterLink | MenuToggleTrigger
|
|
21
|
+
|
|
22
|
+
export class Menu {
|
|
23
|
+
items: ShallowReactive<Map<symbol, MenuItem>> = shallowReactive(new Map())
|
|
24
|
+
|
|
25
|
+
isActive = computed(() => Array.from(this.items.values()).some(item => item.isActive.value))
|
|
26
|
+
|
|
27
|
+
constructor(public context: MenuContext) {}
|
|
28
|
+
|
|
29
|
+
addItem(item: MenuItem) {
|
|
30
|
+
this.items.set(item.id, item)
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function useMenu<const TStructure extends MenuStructure>(
|
|
35
|
+
structure: MaybeRefOrGetter<TStructure>
|
|
36
|
+
): ComputedRef<ParseStructure<TStructure> & WithMenu> & WithMenu {
|
|
37
|
+
const context = new MenuContext()
|
|
38
|
+
|
|
39
|
+
const menu = new Menu(context)
|
|
40
|
+
|
|
41
|
+
return extendRef(
|
|
42
|
+
computed(() => {
|
|
43
|
+
return {
|
|
44
|
+
...parseStructure(menu, structure),
|
|
45
|
+
[MENU_SYMBOL]: menu,
|
|
46
|
+
}
|
|
47
|
+
}),
|
|
48
|
+
{ [MENU_SYMBOL]: menu }
|
|
49
|
+
)
|
|
50
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { BaseItem, type Menu, type MenuLike, parseConfigHolder } from '@core/packages/menu'
|
|
2
|
+
import { computed, markRaw, type MaybeRefOrGetter, reactive, toValue } from 'vue'
|
|
3
|
+
import { type RouteLocationRaw, RouterLink } from 'vue-router'
|
|
4
|
+
|
|
5
|
+
export interface MenuRouterLinkConfig {
|
|
6
|
+
to: MaybeRefOrGetter<RouteLocationRaw>
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export class MenuRouterLinkConfigHolder {
|
|
10
|
+
constructor(public config: MenuRouterLinkConfig) {}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface MenuRouterLinkProps {
|
|
14
|
+
as: typeof RouterLink
|
|
15
|
+
to: RouteLocationRaw
|
|
16
|
+
onClick: () => void
|
|
17
|
+
onMouseenter: () => void
|
|
18
|
+
'data-menu-id': string
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export class MenuRouterLink extends BaseItem {
|
|
22
|
+
constructor(
|
|
23
|
+
public menu: Menu,
|
|
24
|
+
public config: MenuRouterLinkConfig
|
|
25
|
+
) {
|
|
26
|
+
super(menu)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
get props(): MenuRouterLinkProps {
|
|
30
|
+
return reactive({
|
|
31
|
+
as: markRaw(RouterLink),
|
|
32
|
+
onMouseenter: () => this.activate(),
|
|
33
|
+
onClick: () => this.deactivate(),
|
|
34
|
+
to: computed(() => toValue(this.config.to)),
|
|
35
|
+
'data-menu-id': this.menu.context.id,
|
|
36
|
+
})
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function routerLink(to: MaybeRefOrGetter<RouteLocationRaw>, config: Omit<MenuRouterLinkConfig, 'to'> = {}) {
|
|
41
|
+
return new MenuRouterLinkConfigHolder({
|
|
42
|
+
...config,
|
|
43
|
+
to,
|
|
44
|
+
})
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function useMenuRouterLink(config: MenuRouterLinkConfig & { parent: MenuLike }) {
|
|
48
|
+
const { parent, to, ...configRest } = config
|
|
49
|
+
|
|
50
|
+
return parseConfigHolder(parent, routerLink(to, configRest))
|
|
51
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Menu,
|
|
3
|
+
MENU_SYMBOL,
|
|
4
|
+
MenuAction,
|
|
5
|
+
type MenuActionProps,
|
|
6
|
+
MenuActionConfigHolder,
|
|
7
|
+
type MenuLike,
|
|
8
|
+
MenuLink,
|
|
9
|
+
type MenuLinkProps,
|
|
10
|
+
MenuLinkConfigHolder,
|
|
11
|
+
MenuRouterLink,
|
|
12
|
+
type MenuRouterLinkProps,
|
|
13
|
+
MenuRouterLinkConfigHolder,
|
|
14
|
+
type MenuToggleProps,
|
|
15
|
+
MenuToggleConfigHolder,
|
|
16
|
+
MenuToggleTarget,
|
|
17
|
+
MenuToggleTrigger,
|
|
18
|
+
} from '@core/packages/menu'
|
|
19
|
+
import { type MaybeRefOrGetter, toValue } from 'vue'
|
|
20
|
+
|
|
21
|
+
export type MenuStructure = {
|
|
22
|
+
[K: string]: ConfigHolder
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export type ConfigHolder =
|
|
26
|
+
| MenuActionConfigHolder
|
|
27
|
+
| MenuLinkConfigHolder
|
|
28
|
+
| MenuRouterLinkConfigHolder
|
|
29
|
+
| MenuToggleConfigHolder<any>
|
|
30
|
+
|
|
31
|
+
export type ParseConfigHolder<TConfigHolder extends ConfigHolder> = TConfigHolder extends MenuActionConfigHolder
|
|
32
|
+
? MenuActionProps
|
|
33
|
+
: TConfigHolder extends MenuLinkConfigHolder
|
|
34
|
+
? MenuLinkProps
|
|
35
|
+
: TConfigHolder extends MenuRouterLinkConfigHolder
|
|
36
|
+
? MenuRouterLinkProps
|
|
37
|
+
: TConfigHolder extends MenuToggleConfigHolder<infer TItems>
|
|
38
|
+
? MenuToggleProps<TItems>
|
|
39
|
+
: never
|
|
40
|
+
|
|
41
|
+
export type ParseStructure<TStructure extends MenuStructure> = {
|
|
42
|
+
[K in keyof TStructure]: ParseConfigHolder<TStructure[K]>
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function parseConfigHolder<TConfigHolder extends ConfigHolder>(
|
|
46
|
+
menuLike: MenuLike,
|
|
47
|
+
configHolder: TConfigHolder
|
|
48
|
+
): ParseConfigHolder<TConfigHolder> {
|
|
49
|
+
const menu = menuLike instanceof Menu ? menuLike : menuLike[MENU_SYMBOL]
|
|
50
|
+
|
|
51
|
+
if (configHolder instanceof MenuActionConfigHolder) {
|
|
52
|
+
return new MenuAction(menu, configHolder.config).props as ParseConfigHolder<TConfigHolder>
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (configHolder instanceof MenuLinkConfigHolder) {
|
|
56
|
+
return new MenuLink(menu, configHolder.config).props as ParseConfigHolder<TConfigHolder>
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (configHolder instanceof MenuRouterLinkConfigHolder) {
|
|
60
|
+
return new MenuRouterLink(menu, configHolder.config).props as ParseConfigHolder<TConfigHolder>
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (configHolder instanceof MenuToggleConfigHolder) {
|
|
64
|
+
const trigger = new MenuToggleTrigger(menu, configHolder.config)
|
|
65
|
+
const target = new MenuToggleTarget(trigger, configHolder.config)
|
|
66
|
+
|
|
67
|
+
return {
|
|
68
|
+
$trigger: trigger.props,
|
|
69
|
+
$target: target.props,
|
|
70
|
+
$isOpen: trigger.isOpen,
|
|
71
|
+
[MENU_SYMBOL]: trigger.subMenu,
|
|
72
|
+
...parseStructure(trigger.subMenu, configHolder.config.items),
|
|
73
|
+
} as ParseConfigHolder<TConfigHolder>
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
throw new Error('Unsupported config')
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function parseStructure<TStructure extends MenuStructure>(
|
|
80
|
+
menu: Menu,
|
|
81
|
+
structure: MaybeRefOrGetter<TStructure>
|
|
82
|
+
): ParseStructure<TStructure> {
|
|
83
|
+
return Object.fromEntries(
|
|
84
|
+
Object.entries(toValue(structure)).map(([key, configHolder]) => {
|
|
85
|
+
return [key, parseConfigHolder(menu, configHolder)]
|
|
86
|
+
})
|
|
87
|
+
) as ParseStructure<TStructure>
|
|
88
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import type { MenuToggleTrigger } from '@core/packages/menu'
|
|
2
|
+
import { autoUpdate, flip, type Placement, shift, useFloating, type UseFloatingReturn } from '@floating-ui/vue'
|
|
3
|
+
import { unrefElement } from '@vueuse/core'
|
|
4
|
+
import { computed, reactive, ref, type Ref, toValue, type UnwrapRef } from 'vue'
|
|
5
|
+
|
|
6
|
+
export interface MenuToggleTargetConfig {
|
|
7
|
+
placement?: Placement
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface MenuToggleTargetProps {
|
|
11
|
+
ref: (el: any) => void
|
|
12
|
+
style: {
|
|
13
|
+
display: string | undefined
|
|
14
|
+
zIndex: number
|
|
15
|
+
} & UnwrapRef<UseFloatingReturn['floatingStyles']>
|
|
16
|
+
'data-menu-id': string
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export class MenuToggleTarget {
|
|
20
|
+
element: Ref<HTMLElement | null> = ref(null)
|
|
21
|
+
|
|
22
|
+
styles: UseFloatingReturn['floatingStyles']
|
|
23
|
+
|
|
24
|
+
constructor(
|
|
25
|
+
public trigger: MenuToggleTrigger,
|
|
26
|
+
public config: MenuToggleTargetConfig
|
|
27
|
+
) {
|
|
28
|
+
const { floatingStyles } = useFloating(trigger.element, this.element, {
|
|
29
|
+
placement: computed(() => toValue(config.placement) ?? 'bottom-start'),
|
|
30
|
+
open: trigger.isOpen,
|
|
31
|
+
whileElementsMounted: autoUpdate,
|
|
32
|
+
middleware: [shift(), flip()],
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
this.styles = floatingStyles
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
get props(): MenuToggleTargetProps {
|
|
39
|
+
return reactive({
|
|
40
|
+
as: 'button',
|
|
41
|
+
type: 'button',
|
|
42
|
+
ref: (element: HTMLElement | null | undefined) => {
|
|
43
|
+
const newElement = unrefElement(element)
|
|
44
|
+
|
|
45
|
+
if (newElement !== this.element.value) {
|
|
46
|
+
this.element.value = newElement ?? null
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
style: computed(() => {
|
|
50
|
+
return {
|
|
51
|
+
display: this.trigger.isOpen.value ? undefined : 'none',
|
|
52
|
+
zIndex: 9999,
|
|
53
|
+
...this.styles.value,
|
|
54
|
+
}
|
|
55
|
+
}),
|
|
56
|
+
'data-menu-id': this.trigger.menu.context.id,
|
|
57
|
+
})
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { BaseItem, Menu } from '@core/packages/menu'
|
|
2
|
+
import { unrefElement, whenever } from '@vueuse/core'
|
|
3
|
+
import { computed, type ComputedRef, type MaybeRefOrGetter, reactive, ref, type Ref, toValue } from 'vue'
|
|
4
|
+
|
|
5
|
+
export interface MenuToggleTriggerConfig {
|
|
6
|
+
behavior?: MaybeRefOrGetter<'click' | 'mouseenter'>
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface MenuToggleTriggerProps {
|
|
10
|
+
ref: (el: any) => void
|
|
11
|
+
as: 'button'
|
|
12
|
+
type: 'button'
|
|
13
|
+
submenu: true
|
|
14
|
+
onClick: () => void
|
|
15
|
+
onMouseenter: () => void
|
|
16
|
+
selected: boolean
|
|
17
|
+
'data-menu-id': string
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export class MenuToggleTrigger extends BaseItem {
|
|
21
|
+
element: Ref<HTMLElement | null> = ref(null)
|
|
22
|
+
|
|
23
|
+
subMenu: Menu
|
|
24
|
+
|
|
25
|
+
isSelfOpen = ref(false)
|
|
26
|
+
|
|
27
|
+
isOpen = computed(() => this.isSelfOpen.value || this.subMenu.isActive.value)
|
|
28
|
+
|
|
29
|
+
constructor(
|
|
30
|
+
public menu: Menu,
|
|
31
|
+
public config: MenuToggleTriggerConfig
|
|
32
|
+
) {
|
|
33
|
+
super(menu)
|
|
34
|
+
this.subMenu = new Menu(menu.context)
|
|
35
|
+
|
|
36
|
+
whenever(
|
|
37
|
+
() => !this.isActive.value && !this.subMenu.isActive.value,
|
|
38
|
+
() => {
|
|
39
|
+
this.isSelfOpen.value = false
|
|
40
|
+
}
|
|
41
|
+
)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
get isActive(): ComputedRef<boolean> {
|
|
45
|
+
return computed(() => super.isActive.value || this.subMenu?.isActive.value)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
get props(): MenuToggleTriggerProps {
|
|
49
|
+
return reactive({
|
|
50
|
+
ref: (element: HTMLElement | null | undefined) => {
|
|
51
|
+
const newElement = unrefElement(element)
|
|
52
|
+
|
|
53
|
+
if (newElement !== this.element.value) {
|
|
54
|
+
this.element.value = newElement ?? null
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
as: 'button',
|
|
58
|
+
type: 'button',
|
|
59
|
+
submenu: true,
|
|
60
|
+
onClick: () => (this.isSelfOpen.value = !this.isOpen.value),
|
|
61
|
+
onMouseenter: () => {
|
|
62
|
+
this.activate()
|
|
63
|
+
|
|
64
|
+
if (toValue(this.config.behavior) === 'mouseenter') {
|
|
65
|
+
this.isSelfOpen.value = true
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
selected: computed(() => this.isOpen.value),
|
|
69
|
+
'data-menu-id': this.menu.context.id,
|
|
70
|
+
})
|
|
71
|
+
}
|
|
72
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Menu,
|
|
3
|
+
MenuContext,
|
|
4
|
+
type MenuLike,
|
|
5
|
+
type MenuStructure,
|
|
6
|
+
type MenuToggleTargetConfig,
|
|
7
|
+
type MenuToggleTargetProps,
|
|
8
|
+
type MenuToggleTriggerConfig,
|
|
9
|
+
type MenuToggleTriggerProps,
|
|
10
|
+
parseConfigHolder,
|
|
11
|
+
type ParseStructure,
|
|
12
|
+
type WithMenu,
|
|
13
|
+
} from '@core/packages/menu'
|
|
14
|
+
import { computed, type ComputedRef, type MaybeRefOrGetter } from 'vue'
|
|
15
|
+
|
|
16
|
+
export type MenuToggleConfig<TStructure extends MenuStructure> = MenuToggleTriggerConfig &
|
|
17
|
+
MenuToggleTargetConfig & {
|
|
18
|
+
items?: MaybeRefOrGetter<TStructure>
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export class MenuToggleConfigHolder<TStructure extends MenuStructure> {
|
|
22
|
+
constructor(public config: MenuToggleConfig<TStructure>) {}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export type MenuToggleProps<TStructure extends MenuStructure> = WithMenu &
|
|
26
|
+
ParseStructure<TStructure> & {
|
|
27
|
+
$trigger: MenuToggleTriggerProps
|
|
28
|
+
$target: MenuToggleTargetProps
|
|
29
|
+
$isOpen: ComputedRef<boolean>
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function toggle<const TStructure extends MenuStructure>(config: MenuToggleConfig<TStructure>) {
|
|
33
|
+
return new MenuToggleConfigHolder(config)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function useMenuToggle<const TStructure extends MenuStructure>(
|
|
37
|
+
config: MenuToggleConfig<TStructure> & { parent?: MenuLike } = {}
|
|
38
|
+
) {
|
|
39
|
+
const { parent, ...toggleConfig } = config
|
|
40
|
+
const menu = parent ?? new Menu(new MenuContext())
|
|
41
|
+
|
|
42
|
+
return computed(() => parseConfigHolder(menu, toggle<TStructure>(toggleConfig)))
|
|
43
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xen-orchestra/web-core",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.8.0",
|
|
5
5
|
"private": false,
|
|
6
6
|
"exports": {
|
|
7
7
|
"./*": {
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
}
|
|
11
11
|
},
|
|
12
12
|
"dependencies": {
|
|
13
|
+
"@floating-ui/vue": "^1.1.5",
|
|
13
14
|
"@fontsource/poppins": "^5.0.14",
|
|
14
15
|
"@fortawesome/fontawesome-common-types": "^6.5.1",
|
|
15
16
|
"@fortawesome/free-regular-svg-icons": "^6.5.1",
|