@owdproject/nuxt-kit-nuxt-ui 0.0.0-stage → 4.0.0-alpha.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 (65) hide show
  1. package/dist/module.d.mts +14 -0
  2. package/dist/module.json +9 -0
  3. package/dist/module.mjs +90 -0
  4. package/dist/runtime/components/dialogs/NuxtUIAlertModal.d.vue.ts +8 -0
  5. package/dist/runtime/components/dialogs/NuxtUIAlertModal.vue +40 -0
  6. package/dist/runtime/components/dialogs/NuxtUIAlertModal.vue.d.ts +8 -0
  7. package/dist/runtime/components/dialogs/NuxtUIConfirmModal.d.vue.ts +12 -0
  8. package/dist/runtime/components/dialogs/NuxtUIConfirmModal.vue +47 -0
  9. package/dist/runtime/components/dialogs/NuxtUIConfirmModal.vue.d.ts +12 -0
  10. package/dist/runtime/components/explorer/AddressBar.d.vue.ts +17 -0
  11. package/dist/runtime/components/explorer/AddressBar.vue +39 -0
  12. package/dist/runtime/components/explorer/AddressBar.vue.d.ts +17 -0
  13. package/dist/runtime/components/explorer/Chrome.d.vue.ts +13 -0
  14. package/dist/runtime/components/explorer/Chrome.vue +5 -0
  15. package/dist/runtime/components/explorer/Chrome.vue.d.ts +13 -0
  16. package/dist/runtime/components/explorer/DefaultToolbar.d.vue.ts +31 -0
  17. package/dist/runtime/components/explorer/DefaultToolbar.vue +92 -0
  18. package/dist/runtime/components/explorer/DefaultToolbar.vue.d.ts +31 -0
  19. package/dist/runtime/components/explorer/FileContextMenu.d.vue.ts +18 -0
  20. package/dist/runtime/components/explorer/FileContextMenu.vue +90 -0
  21. package/dist/runtime/components/explorer/FileContextMenu.vue.d.ts +18 -0
  22. package/dist/runtime/components/explorer/FileEntry.d.vue.ts +109 -0
  23. package/dist/runtime/components/explorer/FileEntry.vue +263 -0
  24. package/dist/runtime/components/explorer/FileEntry.vue.d.ts +109 -0
  25. package/dist/runtime/components/explorer/FileIcon.d.vue.ts +29 -0
  26. package/dist/runtime/components/explorer/FileIcon.vue +41 -0
  27. package/dist/runtime/components/explorer/FileIcon.vue.d.ts +29 -0
  28. package/dist/runtime/components/explorer/FilePane.d.vue.ts +13 -0
  29. package/dist/runtime/components/explorer/FilePane.vue +5 -0
  30. package/dist/runtime/components/explorer/FilePane.vue.d.ts +13 -0
  31. package/dist/runtime/components/explorer/Frame.d.vue.ts +75 -0
  32. package/dist/runtime/components/explorer/Frame.vue +73 -0
  33. package/dist/runtime/components/explorer/Frame.vue.d.ts +75 -0
  34. package/dist/runtime/components/explorer/Nav.d.vue.ts +17 -0
  35. package/dist/runtime/components/explorer/Nav.vue +83 -0
  36. package/dist/runtime/components/explorer/Nav.vue.d.ts +17 -0
  37. package/dist/runtime/components/explorer/SelectableArea.d.vue.ts +33 -0
  38. package/dist/runtime/components/explorer/SelectableArea.vue +83 -0
  39. package/dist/runtime/components/explorer/SelectableArea.vue.d.ts +33 -0
  40. package/dist/runtime/components/explorer/Workspace.d.vue.ts +24 -0
  41. package/dist/runtime/components/explorer/Workspace.vue +116 -0
  42. package/dist/runtime/components/explorer/Workspace.vue.d.ts +24 -0
  43. package/dist/runtime/dialogs/createDesktopDialogs.d.ts +2 -0
  44. package/dist/runtime/dialogs/createDesktopDialogs.js +47 -0
  45. package/dist/runtime/plugins/01.desktop-nuxt-ui-dialogs.client.d.ts +2 -0
  46. package/dist/runtime/plugins/01.desktop-nuxt-ui-dialogs.client.js +13 -0
  47. package/dist/types.d.mts +9 -0
  48. package/package.json +78 -4
  49. package/src/kit/registerTailwindPath.ts +44 -0
  50. package/src/runtime/components/dialogs/NuxtUIAlertModal.vue +42 -0
  51. package/src/runtime/components/dialogs/NuxtUIConfirmModal.vue +49 -0
  52. package/src/runtime/components/explorer/AddressBar.vue +44 -0
  53. package/src/runtime/components/explorer/Chrome.vue +5 -0
  54. package/src/runtime/components/explorer/DefaultToolbar.vue +92 -0
  55. package/src/runtime/components/explorer/FileContextMenu.vue +99 -0
  56. package/src/runtime/components/explorer/FileEntry.vue +286 -0
  57. package/src/runtime/components/explorer/FileIcon.vue +43 -0
  58. package/src/runtime/components/explorer/FilePane.vue +5 -0
  59. package/src/runtime/components/explorer/Frame.vue +106 -0
  60. package/src/runtime/components/explorer/Nav.vue +87 -0
  61. package/src/runtime/components/explorer/SelectableArea.vue +93 -0
  62. package/src/runtime/components/explorer/Workspace.vue +119 -0
  63. package/src/runtime/dialogs/createDesktopDialogs.ts +56 -0
  64. package/src/runtime/plugins/01.desktop-nuxt-ui-dialogs.client.ts +14 -0
  65. package/README.md +0 -3
@@ -0,0 +1,47 @@
1
+ import { useModal } from "#imports";
2
+ import NuxtUIConfirmModal from "../components/dialogs/NuxtUIConfirmModal.vue";
3
+ import NuxtUIAlertModal from "../components/dialogs/NuxtUIAlertModal.vue";
4
+ export function createDesktopDialogs() {
5
+ return {
6
+ confirm(opts) {
7
+ return new Promise((resolve) => {
8
+ const modal = useModal();
9
+ const toTrash = opts.extras?.toTrash;
10
+ modal.open(NuxtUIConfirmModal, {
11
+ title: opts.title ?? "Confirm",
12
+ message: typeof opts.message === "string" ? opts.message : opts.message?.message ?? "",
13
+ acceptLabel: opts.acceptLabel ?? "OK",
14
+ rejectLabel: opts.rejectLabel ?? "Cancel",
15
+ toTrash: !!toTrash,
16
+ onSuccess: () => {
17
+ modal.close();
18
+ resolve(true);
19
+ },
20
+ onCancel: () => {
21
+ modal.close();
22
+ resolve(false);
23
+ }
24
+ });
25
+ });
26
+ },
27
+ alert(message, options) {
28
+ return new Promise((resolve) => {
29
+ const modal = useModal();
30
+ modal.open(NuxtUIAlertModal, {
31
+ title: options?.title ?? "Alert",
32
+ message,
33
+ onSuccess: () => {
34
+ modal.close();
35
+ resolve();
36
+ }
37
+ });
38
+ });
39
+ },
40
+ prompt(message, defaultValue = "") {
41
+ if (import.meta.server) return Promise.resolve(null);
42
+ const g = globalThis;
43
+ const v = typeof g.prompt === "function" ? g.prompt(message, defaultValue) : null;
44
+ return Promise.resolve(v === null ? null : v);
45
+ }
46
+ };
47
+ }
@@ -0,0 +1,2 @@
1
+ declare const _default: import("nuxt/app").Plugin<Record<string, unknown>> & import("nuxt/app").ObjectPlugin<Record<string, unknown>>;
2
+ export default _default;
@@ -0,0 +1,13 @@
1
+ import { defineNuxtPlugin } from "nuxt/app";
2
+ import { DESKTOP_DIALOG_PROVIDER_KEY } from "@owdproject/nuxt/runtime/constants/desktopShellKeys";
3
+ import { createDesktopDialogs } from "../dialogs/createDesktopDialogs.js";
4
+ export default defineNuxtPlugin({
5
+ name: "owd-kit-nuxt-ui-dialogs",
6
+ enforce: "pre",
7
+ setup(nuxtApp) {
8
+ nuxtApp.vueApp.provide(
9
+ DESKTOP_DIALOG_PROVIDER_KEY,
10
+ createDesktopDialogs()
11
+ );
12
+ }
13
+ });
@@ -0,0 +1,9 @@
1
+ import type { NuxtModule } from '@nuxt/schema'
2
+
3
+ import type { default as Module } from './module.mjs'
4
+
5
+ export type ModuleOptions = typeof Module extends NuxtModule<infer O> ? Partial<O> : Record<string, any>
6
+
7
+ export { default } from './module.mjs'
8
+
9
+ export { type KitNuxtUiModuleOptions } from './module.mjs'
package/package.json CHANGED
@@ -1,6 +1,80 @@
1
1
  {
2
2
  "name": "@owdproject/nuxt-kit-nuxt-ui",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "4.0.0-alpha.3",
4
+ "description": "Nuxt UI for OWD themes: dialogs (always) and optional DesktopExplorer* UI (requires @owdproject/nuxt-module-fs; use { explorer: false } for dialogs only).",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "exports": {
8
+ ".": {
9
+ "types": "./dist/types.d.mts",
10
+ "development": "./src/module.ts",
11
+ "import": "./dist/module.mjs",
12
+ "default": "./dist/module.mjs"
13
+ },
14
+ "./runtime/dialogs/createDesktopDialogs": {
15
+ "types": "./src/runtime/dialogs/createDesktopDialogs.ts",
16
+ "import": "./src/runtime/dialogs/createDesktopDialogs.ts"
17
+ },
18
+ "./runtime/dialogs/*": {
19
+ "types": "./src/runtime/dialogs/*",
20
+ "import": "./src/runtime/dialogs/*"
21
+ },
22
+ "./runtime/components/*": {
23
+ "types": "./src/runtime/components/*",
24
+ "import": "./src/runtime/components/*"
25
+ },
26
+ "./kit/registerTailwindPath": {
27
+ "types": "./src/kit/registerTailwindPath.ts",
28
+ "import": "./src/kit/registerTailwindPath.ts"
29
+ }
30
+ },
31
+ "main": "./dist/module.mjs",
32
+ "typesVersions": {
33
+ "*": {
34
+ ".": [
35
+ "./dist/types.d.mts"
36
+ ]
37
+ }
38
+ },
39
+ "files": [
40
+ "dist",
41
+ "src/runtime",
42
+ "src/kit"
43
+ ],
44
+ "scripts": {
45
+ "prepack": "nuxt-module-build build",
46
+ "dev": "pnpm run dev:prepare",
47
+ "dev:prepare": "nuxt-module-build build --stub",
48
+ "build:module": "nuxt-module-build build"
49
+ },
50
+ "dependencies": {
51
+ "@nuxt/kit": "^4.4.4",
52
+ "@nuxt/ui": "^2.21.0",
53
+ "defu": "^6.1.4",
54
+ "vue3-selecto": "^1.12.3",
55
+ "@owdproject/nuxt": "^4.0.0-alpha.3"
56
+ },
57
+ "devDependencies": {
58
+ "@nuxt/module-builder": "^1.0.2",
59
+ "@nuxt/schema": "^4.4.4",
60
+ "@owdproject/core": "^4.0.0-alpha.3",
61
+ "@owdproject/nuxt-module-fs": "^4.0.0-alpha.3",
62
+ "nuxt": "^4.4.4",
63
+ "typescript": "catalog:"
64
+ },
65
+ "peerDependencies": {
66
+ "@owdproject/core": "^4.0.0-alpha.3",
67
+ "@owdproject/nuxt-module-fs": "^4.0.0-alpha.3",
68
+ "@owdproject/nuxt": "^4.0.0-alpha.3"
69
+ },
70
+ "peerDependenciesMeta": {
71
+ "@owdproject/nuxt-module-fs": {
72
+ "optional": true
73
+ }
74
+ },
75
+ "publishConfig": {
76
+ "access": "public",
77
+ "tag": "alpha",
78
+ "registry": "https://registry.npmjs.org/"
79
+ }
80
+ }
@@ -0,0 +1,44 @@
1
+ import { createResolver } from '@nuxt/kit'
2
+ import type { Nuxt } from '@nuxt/schema'
3
+
4
+ declare module '@nuxt/schema' {
5
+ interface NuxtConfig {
6
+ desktopTailwindContent?: string[]
7
+ }
8
+ }
9
+
10
+ const DEFAULT_THEME_TAILWIND_GLOBS = [
11
+ './runtime/components/**/*.{vue,mjs,ts}',
12
+ './runtime/apps/**/*.{vue,mjs,ts}',
13
+ ]
14
+
15
+ function tailwindContentPaths(nuxt: Nuxt): string[] {
16
+ nuxt.options.desktopTailwindContent ??= []
17
+ return nuxt.options.desktopTailwindContent
18
+ }
19
+
20
+ /**
21
+ * Register a Tailwind content glob; merged on `tailwindcss:config` when kit installs `@nuxtjs/tailwindcss`.
22
+ * Call from themes, built-in apps, or extension modules that ship Vue templates with utility classes.
23
+ */
24
+ export function registerTailwindPath(nuxt: Nuxt, path: string): void {
25
+ const paths = tailwindContentPaths(nuxt)
26
+ if (!paths.includes(path)) {
27
+ paths.push(path)
28
+ }
29
+ }
30
+
31
+ /**
32
+ * Register default theme component globs relative to a theme module root (`import.meta.url`).
33
+ */
34
+ export function registerThemeTailwindPath(
35
+ nuxt: Nuxt,
36
+ moduleUrl: string,
37
+ glob: string | string[] = DEFAULT_THEME_TAILWIND_GLOBS,
38
+ ): void {
39
+ const { resolve } = createResolver(moduleUrl)
40
+ const globs = Array.isArray(glob) ? glob : [glob]
41
+ for (const entry of globs) {
42
+ registerTailwindPath(nuxt, resolve(entry))
43
+ }
44
+ }
@@ -0,0 +1,42 @@
1
+ <script setup lang="ts">
2
+ import { ref } from 'vue'
3
+
4
+ defineProps<{
5
+ title: string
6
+ message: string
7
+ onSuccess: () => void
8
+ }>()
9
+
10
+ const isOpen = ref(true)
11
+ </script>
12
+
13
+ <template>
14
+ <UModal v-model="isOpen" prevent-close>
15
+ <UCard :ui="{ ring: '', divide: 'y divide-gray-100 dark:divide-gray-800' }">
16
+ <template #header>
17
+ <div class="flex items-center justify-between">
18
+ <h3 class="text-base font-semibold leading-6 text-gray-900 dark:text-white">
19
+ {{ title }}
20
+ </h3>
21
+ </div>
22
+ </template>
23
+
24
+ <div class="flex items-start space-x-4">
25
+ <div class="text-blue-500 flex-shrink-0 mt-0.5">
26
+ <UIcon name="i-heroicons-information-circle" class="w-6 h-6" />
27
+ </div>
28
+ <div class="text-sm text-gray-600 dark:text-gray-300">
29
+ {{ message }}
30
+ </div>
31
+ </div>
32
+
33
+ <template #footer>
34
+ <div class="flex justify-end">
35
+ <UButton color="primary" @click="onSuccess">
36
+ OK
37
+ </UButton>
38
+ </div>
39
+ </template>
40
+ </UCard>
41
+ </UModal>
42
+ </template>
@@ -0,0 +1,49 @@
1
+ <script setup lang="ts">
2
+ import { ref } from 'vue'
3
+
4
+ defineProps<{
5
+ title: string
6
+ message: string
7
+ acceptLabel: string
8
+ rejectLabel: string
9
+ toTrash?: boolean
10
+ onSuccess: () => void
11
+ onCancel: () => void
12
+ }>()
13
+
14
+ const isOpen = ref(true)
15
+ </script>
16
+
17
+ <template>
18
+ <UModal v-model="isOpen" prevent-close>
19
+ <UCard :ui="{ ring: '', divide: 'y divide-gray-100 dark:divide-gray-800' }">
20
+ <template #header>
21
+ <div class="flex items-center justify-between">
22
+ <h3 class="text-base font-semibold leading-6 text-gray-900 dark:text-white">
23
+ {{ title }}
24
+ </h3>
25
+ </div>
26
+ </template>
27
+
28
+ <div class="flex items-start space-x-4">
29
+ <div class="text-amber-500 flex-shrink-0 mt-0.5">
30
+ <UIcon name="i-heroicons-exclamation-triangle" class="w-6 h-6" />
31
+ </div>
32
+ <div class="text-sm text-gray-600 dark:text-gray-300">
33
+ {{ message }}
34
+ </div>
35
+ </div>
36
+
37
+ <template #footer>
38
+ <div class="flex justify-end space-x-2">
39
+ <UButton color="gray" variant="ghost" @click="onCancel">
40
+ {{ rejectLabel }}
41
+ </UButton>
42
+ <UButton color="primary" @click="onSuccess">
43
+ {{ acceptLabel }}
44
+ </UButton>
45
+ </div>
46
+ </template>
47
+ </UCard>
48
+ </UModal>
49
+ </template>
@@ -0,0 +1,44 @@
1
+ <script setup lang="ts">
2
+ import { ref, watch } from 'vue'
3
+ import {
4
+ formatExplorerDisplayPath,
5
+ parseExplorerVfsPath,
6
+ } from '@owdproject/nuxt-module-fs/runtime/utils/utilExplorerDisplayPath'
7
+
8
+ const props = defineProps({
9
+ address: { type: String, required: true },
10
+ })
11
+
12
+ const emit = defineEmits(['update:modelValue'])
13
+
14
+ const addressInput = ref(formatExplorerDisplayPath(props.address))
15
+
16
+ watch(
17
+ () => props.address,
18
+ (address) => {
19
+ addressInput.value = formatExplorerDisplayPath(address)
20
+ },
21
+ )
22
+
23
+ function onEnter() {
24
+ emit('update:modelValue', parseExplorerVfsPath(addressInput.value))
25
+ }
26
+ </script>
27
+
28
+ <template>
29
+ <div class="flex items-center space-x-2 p-2 bg-gray-50 dark:bg-gray-900 border-b border-gray-200 dark:border-gray-800">
30
+ <span class="text-xs font-semibold text-gray-500 dark:text-gray-400 select-none">
31
+ {{ $t('apps.explorer.address') }}
32
+ </span>
33
+ <UInput
34
+ v-model="addressInput"
35
+ class="flex-1"
36
+ size="sm"
37
+ color="gray"
38
+ variant="outline"
39
+ spellcheck="false"
40
+ icon="i-heroicons-folder-open"
41
+ @keydown.enter="onEnter"
42
+ />
43
+ </div>
44
+ </template>
@@ -0,0 +1,5 @@
1
+ <template>
2
+ <div class="flex flex-col flex-1 min-h-0 bg-white dark:bg-gray-950 text-gray-900 dark:text-gray-100">
3
+ <slot />
4
+ </div>
5
+ </template>
@@ -0,0 +1,92 @@
1
+ <script setup lang="ts">
2
+ defineProps({
3
+ arrowsDisabled: { type: Boolean, required: true },
4
+ })
5
+ defineEmits([
6
+ 'back',
7
+ 'forward',
8
+ 'up',
9
+ 'cut',
10
+ 'copy',
11
+ 'paste',
12
+ 'delete',
13
+ 'properties',
14
+ ])
15
+ </script>
16
+
17
+ <template>
18
+ <div class="flex items-center space-x-1 p-2 bg-gray-50 dark:bg-gray-900 border-b border-gray-200 dark:border-gray-800">
19
+ <UButton
20
+ icon="i-heroicons-arrow-left"
21
+ color="gray"
22
+ variant="ghost"
23
+ size="sm"
24
+ :disabled="arrowsDisabled"
25
+ @click="$emit('back')"
26
+ :title="$t('apps.explorer.action.back')"
27
+ />
28
+ <UButton
29
+ icon="i-heroicons-arrow-right"
30
+ color="gray"
31
+ variant="ghost"
32
+ size="sm"
33
+ :disabled="arrowsDisabled"
34
+ @click="$emit('forward')"
35
+ :title="$t('apps.explorer.action.forward')"
36
+ />
37
+ <UButton
38
+ icon="i-heroicons-arrow-up-circle"
39
+ color="gray"
40
+ variant="ghost"
41
+ size="sm"
42
+ @click="$emit('up')"
43
+ :title="$t('apps.explorer.action.up')"
44
+ />
45
+
46
+ <div class="border-l border-gray-200 dark:border-gray-800 h-5 mx-1" />
47
+
48
+ <UButton
49
+ icon="i-heroicons-scissors"
50
+ color="gray"
51
+ variant="ghost"
52
+ size="sm"
53
+ @click="$emit('cut')"
54
+ :title="$t('apps.explorer.action.cut')"
55
+ />
56
+ <UButton
57
+ icon="i-heroicons-document-duplicate"
58
+ color="gray"
59
+ variant="ghost"
60
+ size="sm"
61
+ @click="$emit('copy')"
62
+ :title="$t('apps.explorer.action.copy')"
63
+ />
64
+ <UButton
65
+ icon="i-heroicons-clipboard"
66
+ color="gray"
67
+ variant="ghost"
68
+ size="sm"
69
+ @click="$emit('paste')"
70
+ :title="$t('apps.explorer.action.paste')"
71
+ />
72
+ <UButton
73
+ icon="i-heroicons-trash"
74
+ color="red"
75
+ variant="ghost"
76
+ size="sm"
77
+ @click="$emit('delete')"
78
+ :title="$t('apps.explorer.action.delete')"
79
+ />
80
+
81
+ <div class="border-l border-gray-200 dark:border-gray-800 h-5 mx-1" />
82
+
83
+ <UButton
84
+ icon="i-heroicons-information-circle"
85
+ color="gray"
86
+ variant="ghost"
87
+ size="sm"
88
+ @click="$emit('properties')"
89
+ :title="$t('apps.explorer.action.properties')"
90
+ />
91
+ </div>
92
+ </template>
@@ -0,0 +1,99 @@
1
+ <script setup lang="ts">
2
+ import { ref, computed } from 'vue'
3
+ import { useI18n } from 'vue-i18n'
4
+ import { explorerEntryAbsolutePath } from '@owdproject/nuxt-module-fs/runtime/utils/utilExplorerEntryPath'
5
+
6
+ const { t } = useI18n()
7
+
8
+ const props = defineProps<{
9
+ fileName: string
10
+ window: any
11
+ isDirectory?: boolean
12
+ openPathInNewTab?: (p: string) => void
13
+ }>()
14
+
15
+ const emit = defineEmits(['open', 'rename'])
16
+
17
+ const isOpen = ref(false)
18
+ const virtualElement = ref({ getBoundingClientRect: () => ({} as any) })
19
+
20
+ const entryPath = computed(() =>
21
+ explorerEntryAbsolutePath(
22
+ props.window.fsExplorer.basePath.value,
23
+ props.fileName,
24
+ ),
25
+ )
26
+
27
+ function selectEntry() {
28
+ props.window.fsExplorer.selectFiles([entryPath.value])
29
+ }
30
+
31
+ const items = computed(() => [
32
+ [
33
+ {
34
+ label: t('fs.contextMenu.open'),
35
+ click: () => {
36
+ emit('open', props.fileName)
37
+ },
38
+ }
39
+ ],
40
+ [
41
+ {
42
+ label: t('fs.contextMenu.cut'),
43
+ click: () => {
44
+ selectEntry()
45
+ props.window.fsExplorer.cutSelectedFiles()
46
+ },
47
+ },
48
+ {
49
+ label: t('fs.contextMenu.copy'),
50
+ click: () => {
51
+ selectEntry()
52
+ props.window.fsExplorer.copySelectedFiles()
53
+ },
54
+ }
55
+ ],
56
+ [
57
+ {
58
+ label: t('fs.contextMenu.delete'),
59
+ click: () => {
60
+ selectEntry()
61
+ props.window.fsExplorer.fsController.deleteSelectedFiles()
62
+ },
63
+ },
64
+ {
65
+ label: t('fs.contextMenu.rename'),
66
+ click: () => {
67
+ emit('rename', props.fileName)
68
+ },
69
+ }
70
+ ]
71
+ ])
72
+
73
+ function show(event: MouseEvent) {
74
+ event.preventDefault()
75
+ virtualElement.value = {
76
+ getBoundingClientRect: () => ({
77
+ width: 0,
78
+ height: 0,
79
+ top: event.clientY,
80
+ right: event.clientX,
81
+ bottom: event.clientY,
82
+ left: event.clientX,
83
+ }) as any
84
+ }
85
+ isOpen.value = true
86
+ }
87
+
88
+ defineExpose({
89
+ show,
90
+ })
91
+ </script>
92
+
93
+ <template>
94
+ <UContextMenu v-model="isOpen" :virtual-element="virtualElement">
95
+ <UCard :ui="{ body: { padding: 'p-1' } }">
96
+ <UVerticalNavigation :links="[items[0], items[1], items[2]]" />
97
+ </UCard>
98
+ </UContextMenu>
99
+ </template>