@jskit-ai/shell-web 0.1.32 → 0.1.34
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/package.descriptor.mjs +111 -33
- package/package.json +8 -2
- package/src/client/components/ShellLayout.vue +11 -4
- package/src/client/components/ShellMenuLinkItem.vue +71 -0
- package/src/client/components/ShellOutlet.vue +10 -7
- package/src/client/components/ShellOutletMenuWidget.vue +57 -0
- package/src/client/components/ShellSurfaceAwareMenuLinkItem.vue +116 -0
- package/src/client/components/ShellTabLinkItem.vue +128 -0
- package/src/client/index.js +4 -0
- package/src/client/lib/menuIcons.js +210 -0
- package/src/client/placement/runtime.js +22 -22
- package/src/client/placement/validators.js +19 -49
- package/src/client/support/menuLinkTarget.js +97 -0
- package/src/server/support/localLinkItemScaffolds.js +80 -0
- package/templates/expected-existing/src/App.vue +13 -0
- package/templates/expected-existing/src/pages/home/index.vue +12 -0
- package/templates/expected-existing/src/pages/home.vue +13 -0
- package/templates/src/components/ShellLayout.vue +11 -4
- package/templates/src/components/menus/MenuLinkItem.vue +30 -0
- package/templates/src/components/menus/SurfaceAwareMenuLinkItem.vue +42 -0
- package/templates/src/components/menus/TabLinkItem.vue +34 -0
- package/templates/src/pages/home/settings/index.vue +8 -8
- package/templates/src/pages/home/settings.vue +4 -1
- package/test/bootstrapClaimContract.test.js +66 -0
- package/test/linkItemScaffoldContract.test.js +209 -0
- package/test/outletMenuWidgetContract.test.js +33 -0
- package/test/placementRegistry.test.js +17 -6
- package/test/placementRuntime.test.js +59 -44
- package/test/settingsPlacementContract.test.js +16 -5
- package/templates/src/pages/console/index.vue +0 -24
- package/templates/src/pages/console.vue +0 -20
package/package.descriptor.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export default Object.freeze({
|
|
2
2
|
packageVersion: 1,
|
|
3
3
|
packageId: "@jskit-ai/shell-web",
|
|
4
|
-
version: "0.1.
|
|
4
|
+
version: "0.1.34",
|
|
5
5
|
kind: "runtime",
|
|
6
6
|
description: "Web shell layout runtime with outlet-based placement contributions.",
|
|
7
7
|
dependsOn: [],
|
|
@@ -30,7 +30,7 @@ export default Object.freeze({
|
|
|
30
30
|
surfaces: [
|
|
31
31
|
{
|
|
32
32
|
subpath: "./client",
|
|
33
|
-
summary: "Exports shell layout/outlet/error-host components and ShellWebClientProvider."
|
|
33
|
+
summary: "Exports shell layout/outlet/outlet-menu/error-host components and ShellWebClientProvider."
|
|
34
34
|
},
|
|
35
35
|
{
|
|
36
36
|
subpath: "./client/placement",
|
|
@@ -55,32 +55,30 @@ export default Object.freeze({
|
|
|
55
55
|
placements: {
|
|
56
56
|
outlets: [
|
|
57
57
|
{
|
|
58
|
-
|
|
59
|
-
position: "top-left",
|
|
58
|
+
target: "shell-layout:top-left",
|
|
60
59
|
surfaces: ["*"],
|
|
61
60
|
source: "src/client/components/ShellLayout.vue"
|
|
62
61
|
},
|
|
63
62
|
{
|
|
64
|
-
|
|
65
|
-
position: "top-right",
|
|
63
|
+
target: "shell-layout:top-right",
|
|
66
64
|
surfaces: ["*"],
|
|
67
65
|
source: "src/client/components/ShellLayout.vue"
|
|
68
66
|
},
|
|
69
67
|
{
|
|
70
|
-
|
|
71
|
-
|
|
68
|
+
target: "shell-layout:primary-menu",
|
|
69
|
+
defaultLinkComponentToken: "local.main.ui.surface-aware-menu-link-item",
|
|
72
70
|
surfaces: ["*"],
|
|
73
71
|
source: "src/client/components/ShellLayout.vue"
|
|
74
72
|
},
|
|
75
73
|
{
|
|
76
|
-
|
|
77
|
-
|
|
74
|
+
target: "shell-layout:secondary-menu",
|
|
75
|
+
defaultLinkComponentToken: "local.main.ui.surface-aware-menu-link-item",
|
|
78
76
|
surfaces: ["*"],
|
|
79
77
|
source: "src/client/components/ShellLayout.vue"
|
|
80
78
|
},
|
|
81
79
|
{
|
|
82
|
-
|
|
83
|
-
|
|
80
|
+
target: "home-settings:primary-menu",
|
|
81
|
+
defaultLinkComponentToken: "local.main.ui.surface-aware-menu-link-item",
|
|
84
82
|
surfaces: ["home"],
|
|
85
83
|
source: "templates/src/pages/home/settings.vue"
|
|
86
84
|
}
|
|
@@ -92,8 +90,9 @@ export default Object.freeze({
|
|
|
92
90
|
mutations: {
|
|
93
91
|
dependencies: {
|
|
94
92
|
runtime: {
|
|
93
|
+
"@mdi/js": "^7.4.47",
|
|
95
94
|
"@tanstack/vue-query": "^5.90.5",
|
|
96
|
-
"@jskit-ai/kernel": "0.1.
|
|
95
|
+
"@jskit-ai/kernel": "0.1.35",
|
|
97
96
|
"vuetify": "^4.0.0"
|
|
98
97
|
},
|
|
99
98
|
dev: {}
|
|
@@ -101,16 +100,78 @@ export default Object.freeze({
|
|
|
101
100
|
packageJson: {
|
|
102
101
|
scripts: {
|
|
103
102
|
"dev:all": "vite",
|
|
104
|
-
"dev:home": "VITE_SURFACE=home vite"
|
|
105
|
-
"dev:console": "VITE_SURFACE=console vite"
|
|
103
|
+
"dev:home": "VITE_SURFACE=home vite"
|
|
106
104
|
}
|
|
107
105
|
},
|
|
108
106
|
procfile: {},
|
|
109
|
-
text: [
|
|
107
|
+
text: [
|
|
108
|
+
{
|
|
109
|
+
op: "append-text",
|
|
110
|
+
file: "packages/main/src/client/providers/MainClientProvider.js",
|
|
111
|
+
position: "top",
|
|
112
|
+
skipIfContains: "import MenuLinkItem from \"/src/components/menus/MenuLinkItem.vue\";",
|
|
113
|
+
value: "import MenuLinkItem from \"/src/components/menus/MenuLinkItem.vue\";\n",
|
|
114
|
+
reason: "Bind app-owned shell menu link-item scaffold into local main client provider imports.",
|
|
115
|
+
category: "shell-web",
|
|
116
|
+
id: "shell-web-main-client-provider-menu-link-item-import"
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
op: "append-text",
|
|
120
|
+
file: "packages/main/src/client/providers/MainClientProvider.js",
|
|
121
|
+
position: "top",
|
|
122
|
+
skipIfContains: "import SurfaceAwareMenuLinkItem from \"/src/components/menus/SurfaceAwareMenuLinkItem.vue\";",
|
|
123
|
+
value: "import SurfaceAwareMenuLinkItem from \"/src/components/menus/SurfaceAwareMenuLinkItem.vue\";\n",
|
|
124
|
+
reason: "Bind app-owned shell surface-aware menu link-item scaffold into local main client provider imports.",
|
|
125
|
+
category: "shell-web",
|
|
126
|
+
id: "shell-web-main-client-provider-surface-aware-menu-link-item-import"
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
op: "append-text",
|
|
130
|
+
file: "packages/main/src/client/providers/MainClientProvider.js",
|
|
131
|
+
position: "top",
|
|
132
|
+
skipIfContains: "import TabLinkItem from \"/src/components/menus/TabLinkItem.vue\";",
|
|
133
|
+
value: "import TabLinkItem from \"/src/components/menus/TabLinkItem.vue\";\n",
|
|
134
|
+
reason: "Bind app-owned shell tab link-item scaffold into local main client provider imports.",
|
|
135
|
+
category: "shell-web",
|
|
136
|
+
id: "shell-web-main-client-provider-tab-link-item-import"
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
op: "append-text",
|
|
140
|
+
file: "packages/main/src/client/providers/MainClientProvider.js",
|
|
141
|
+
position: "bottom",
|
|
142
|
+
skipIfContains: "registerMainClientComponent(\"local.main.ui.menu-link-item\", () => MenuLinkItem);",
|
|
143
|
+
value: "\nregisterMainClientComponent(\"local.main.ui.menu-link-item\", () => MenuLinkItem);\n",
|
|
144
|
+
reason: "Bind app-owned shell menu link-item token into local main client provider registry.",
|
|
145
|
+
category: "shell-web",
|
|
146
|
+
id: "shell-web-main-client-provider-menu-link-item-register"
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
op: "append-text",
|
|
150
|
+
file: "packages/main/src/client/providers/MainClientProvider.js",
|
|
151
|
+
position: "bottom",
|
|
152
|
+
skipIfContains: "registerMainClientComponent(\"local.main.ui.surface-aware-menu-link-item\", () => SurfaceAwareMenuLinkItem);",
|
|
153
|
+
value: "\nregisterMainClientComponent(\"local.main.ui.surface-aware-menu-link-item\", () => SurfaceAwareMenuLinkItem);\n",
|
|
154
|
+
reason: "Bind app-owned shell surface-aware menu link-item token into local main client provider registry.",
|
|
155
|
+
category: "shell-web",
|
|
156
|
+
id: "shell-web-main-client-provider-surface-aware-menu-link-item-register"
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
op: "append-text",
|
|
160
|
+
file: "packages/main/src/client/providers/MainClientProvider.js",
|
|
161
|
+
position: "bottom",
|
|
162
|
+
skipIfContains: "registerMainClientComponent(\"local.main.ui.tab-link-item\", () => TabLinkItem);",
|
|
163
|
+
value: "\nregisterMainClientComponent(\"local.main.ui.tab-link-item\", () => TabLinkItem);\n",
|
|
164
|
+
reason: "Bind app-owned shell tab link-item token into local main client provider registry.",
|
|
165
|
+
category: "shell-web",
|
|
166
|
+
id: "shell-web-main-client-provider-tab-link-item-register"
|
|
167
|
+
}
|
|
168
|
+
],
|
|
110
169
|
files: [
|
|
111
170
|
{
|
|
112
171
|
from: "templates/src/App.vue",
|
|
113
172
|
to: "src/App.vue",
|
|
173
|
+
ownership: "app",
|
|
174
|
+
expectedExistingFrom: "templates/expected-existing/src/App.vue",
|
|
114
175
|
reason: "Install full-width shell app root with shell-web error host and edge-to-edge layout.",
|
|
115
176
|
category: "shell-web",
|
|
116
177
|
id: "shell-web-app-root"
|
|
@@ -118,13 +179,39 @@ export default Object.freeze({
|
|
|
118
179
|
{
|
|
119
180
|
from: "templates/src/components/ShellLayout.vue",
|
|
120
181
|
to: "src/components/ShellLayout.vue",
|
|
182
|
+
ownership: "app",
|
|
121
183
|
reason: "Install app-owned shell layout component so apps can customize structure and slots.",
|
|
122
184
|
category: "shell-web",
|
|
123
185
|
id: "shell-web-component-shell-layout"
|
|
124
186
|
},
|
|
187
|
+
{
|
|
188
|
+
from: "templates/src/components/menus/MenuLinkItem.vue",
|
|
189
|
+
to: "src/components/menus/MenuLinkItem.vue",
|
|
190
|
+
ownership: "app",
|
|
191
|
+
reason: "Install app-owned shell menu link-item scaffold for local placement customization.",
|
|
192
|
+
category: "shell-web",
|
|
193
|
+
id: "shell-web-component-menu-link-item"
|
|
194
|
+
},
|
|
195
|
+
{
|
|
196
|
+
from: "templates/src/components/menus/SurfaceAwareMenuLinkItem.vue",
|
|
197
|
+
to: "src/components/menus/SurfaceAwareMenuLinkItem.vue",
|
|
198
|
+
ownership: "app",
|
|
199
|
+
reason: "Install app-owned surface-aware shell menu link-item scaffold for local placement customization.",
|
|
200
|
+
category: "shell-web",
|
|
201
|
+
id: "shell-web-component-surface-aware-menu-link-item"
|
|
202
|
+
},
|
|
203
|
+
{
|
|
204
|
+
from: "templates/src/components/menus/TabLinkItem.vue",
|
|
205
|
+
to: "src/components/menus/TabLinkItem.vue",
|
|
206
|
+
ownership: "app",
|
|
207
|
+
reason: "Install app-owned shell tab link-item scaffold for local placement customization.",
|
|
208
|
+
category: "shell-web",
|
|
209
|
+
id: "shell-web-component-tab-link-item"
|
|
210
|
+
},
|
|
125
211
|
{
|
|
126
212
|
from: "templates/src/error.js",
|
|
127
213
|
to: "src/error.js",
|
|
214
|
+
ownership: "app",
|
|
128
215
|
reason: "Install app-owned error runtime policy and presenter config scaffold.",
|
|
129
216
|
category: "shell-web",
|
|
130
217
|
id: "shell-web-error-config"
|
|
@@ -132,6 +219,7 @@ export default Object.freeze({
|
|
|
132
219
|
{
|
|
133
220
|
from: "templates/src/placement.js",
|
|
134
221
|
to: "src/placement.js",
|
|
222
|
+
ownership: "app",
|
|
135
223
|
reason: "Install app-owned placement registry scaffold used by shell-web placement runtime.",
|
|
136
224
|
category: "shell-web",
|
|
137
225
|
id: "shell-web-placement-registry"
|
|
@@ -140,6 +228,8 @@ export default Object.freeze({
|
|
|
140
228
|
from: "templates/src/pages/home.vue",
|
|
141
229
|
toSurface: "home",
|
|
142
230
|
toSurfaceRoot: true,
|
|
231
|
+
ownership: "app",
|
|
232
|
+
expectedExistingFrom: "templates/expected-existing/src/pages/home.vue",
|
|
143
233
|
reason: "Install shell-driven home wrapper page.",
|
|
144
234
|
category: "shell-web",
|
|
145
235
|
id: "shell-web-page-home-wrapper"
|
|
@@ -148,6 +238,8 @@ export default Object.freeze({
|
|
|
148
238
|
from: "templates/src/pages/home/index.vue",
|
|
149
239
|
toSurface: "home",
|
|
150
240
|
toSurfacePath: "index.vue",
|
|
241
|
+
ownership: "app",
|
|
242
|
+
expectedExistingFrom: "templates/expected-existing/src/pages/home/index.vue",
|
|
151
243
|
reason: "Install shell-driven home surface starter page.",
|
|
152
244
|
category: "shell-web",
|
|
153
245
|
id: "shell-web-page-home"
|
|
@@ -156,6 +248,7 @@ export default Object.freeze({
|
|
|
156
248
|
from: "templates/src/pages/home/settings.vue",
|
|
157
249
|
toSurface: "home",
|
|
158
250
|
toSurfacePath: "settings.vue",
|
|
251
|
+
ownership: "app",
|
|
159
252
|
reason: "Install shell-driven home settings shell route with section navigation.",
|
|
160
253
|
category: "shell-web",
|
|
161
254
|
id: "shell-web-page-home-settings-shell"
|
|
@@ -164,25 +257,10 @@ export default Object.freeze({
|
|
|
164
257
|
from: "templates/src/pages/home/settings/index.vue",
|
|
165
258
|
toSurface: "home",
|
|
166
259
|
toSurfacePath: "settings/index.vue",
|
|
167
|
-
|
|
260
|
+
ownership: "app",
|
|
261
|
+
reason: "Install shell-driven home settings index stub scaffold for app-owned landing or redirect behavior.",
|
|
168
262
|
category: "shell-web",
|
|
169
263
|
id: "shell-web-page-home-settings"
|
|
170
|
-
},
|
|
171
|
-
{
|
|
172
|
-
from: "templates/src/pages/console.vue",
|
|
173
|
-
toSurface: "console",
|
|
174
|
-
toSurfaceRoot: true,
|
|
175
|
-
reason: "Install shell-driven console wrapper page.",
|
|
176
|
-
category: "shell-web",
|
|
177
|
-
id: "shell-web-page-console-wrapper"
|
|
178
|
-
},
|
|
179
|
-
{
|
|
180
|
-
from: "templates/src/pages/console/index.vue",
|
|
181
|
-
toSurface: "console",
|
|
182
|
-
toSurfacePath: "index.vue",
|
|
183
|
-
reason: "Install shell-driven console page starter.",
|
|
184
|
-
category: "shell-web",
|
|
185
|
-
id: "shell-web-page-console"
|
|
186
264
|
}
|
|
187
265
|
]
|
|
188
266
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jskit-ai/shell-web",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.34",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"test": "node --test"
|
|
@@ -9,16 +9,22 @@
|
|
|
9
9
|
"./client": "./src/client/index.js",
|
|
10
10
|
"./client/error": "./src/client/error/index.js",
|
|
11
11
|
"./client/placement": "./src/client/placement/index.js",
|
|
12
|
+
"./server/support/localLinkItemScaffolds": "./src/server/support/localLinkItemScaffolds.js",
|
|
12
13
|
"./client/navigation/linkResolver": "./src/client/navigation/linkResolver.js",
|
|
13
14
|
"./client/components/ShellLayout": "./src/client/components/ShellLayout.vue",
|
|
14
15
|
"./client/components/ShellOutlet": "./src/client/components/ShellOutlet.vue",
|
|
16
|
+
"./client/components/ShellOutletMenuWidget": "./src/client/components/ShellOutletMenuWidget.vue",
|
|
15
17
|
"./client/components/ShellErrorHost": "./src/client/components/ShellErrorHost.vue",
|
|
18
|
+
"./client/components/ShellMenuLinkItem": "./src/client/components/ShellMenuLinkItem.vue",
|
|
19
|
+
"./client/components/ShellSurfaceAwareMenuLinkItem": "./src/client/components/ShellSurfaceAwareMenuLinkItem.vue",
|
|
20
|
+
"./client/components/ShellTabLinkItem": "./src/client/components/ShellTabLinkItem.vue",
|
|
16
21
|
"./client/composables/useShellLayoutState": "./src/client/composables/useShellLayoutState.js",
|
|
17
22
|
"./client/providers/ShellWebClientProvider": "./src/client/providers/ShellWebClientProvider.js"
|
|
18
23
|
},
|
|
19
24
|
"dependencies": {
|
|
25
|
+
"@mdi/js": "^7.4.47",
|
|
20
26
|
"@tanstack/vue-query": "^5.90.5",
|
|
21
|
-
"@jskit-ai/kernel": "0.1.
|
|
27
|
+
"@jskit-ai/kernel": "0.1.35",
|
|
22
28
|
"vuetify": "^4.0.0"
|
|
23
29
|
}
|
|
24
30
|
}
|
|
@@ -32,7 +32,7 @@ const { drawerOpen, toggleDrawer, resolvedSurface, resolvedSurfaceLabel } = useS
|
|
|
32
32
|
<slot name="top-left" :surface="resolvedSurface">
|
|
33
33
|
<div class="d-flex align-center ga-2">
|
|
34
34
|
<v-chip color="primary" size="small" label>{{ resolvedSurfaceLabel }}</v-chip>
|
|
35
|
-
<ShellOutlet
|
|
35
|
+
<ShellOutlet target="shell-layout:top-left" />
|
|
36
36
|
</div>
|
|
37
37
|
</slot>
|
|
38
38
|
|
|
@@ -40,7 +40,7 @@ const { drawerOpen, toggleDrawer, resolvedSurface, resolvedSurfaceLabel } = useS
|
|
|
40
40
|
|
|
41
41
|
<slot name="top-right" :surface="resolvedSurface">
|
|
42
42
|
<div class="d-flex align-center ga-2">
|
|
43
|
-
<ShellOutlet
|
|
43
|
+
<ShellOutlet target="shell-layout:top-right" />
|
|
44
44
|
</div>
|
|
45
45
|
</slot>
|
|
46
46
|
</v-app-bar>
|
|
@@ -49,9 +49,16 @@ const { drawerOpen, toggleDrawer, resolvedSurface, resolvedSurfaceLabel } = useS
|
|
|
49
49
|
<slot name="menu" :surface="resolvedSurface">
|
|
50
50
|
<v-list nav density="comfortable" class="pt-2">
|
|
51
51
|
<v-list-subheader class="text-uppercase text-caption">{{ resolvedSurfaceLabel }}</v-list-subheader>
|
|
52
|
-
<ShellOutlet
|
|
52
|
+
<ShellOutlet
|
|
53
|
+
target="shell-layout:primary-menu"
|
|
54
|
+
default
|
|
55
|
+
default-link-component-token="local.main.ui.surface-aware-menu-link-item"
|
|
56
|
+
/>
|
|
53
57
|
<v-divider class="my-2" />
|
|
54
|
-
<ShellOutlet
|
|
58
|
+
<ShellOutlet
|
|
59
|
+
target="shell-layout:secondary-menu"
|
|
60
|
+
default-link-component-token="local.main.ui.surface-aware-menu-link-item"
|
|
61
|
+
/>
|
|
55
62
|
</v-list>
|
|
56
63
|
</slot>
|
|
57
64
|
</v-navigation-drawer>
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { computed } from "vue";
|
|
3
|
+
import {
|
|
4
|
+
resolveSurfaceNavigationTargetFromPlacementContext,
|
|
5
|
+
useWebPlacementContext
|
|
6
|
+
} from "../placement/index.js";
|
|
7
|
+
import { resolveMenuLinkIcon } from "../lib/menuIcons.js";
|
|
8
|
+
|
|
9
|
+
const props = defineProps({
|
|
10
|
+
label: {
|
|
11
|
+
type: String,
|
|
12
|
+
default: ""
|
|
13
|
+
},
|
|
14
|
+
to: {
|
|
15
|
+
type: String,
|
|
16
|
+
default: ""
|
|
17
|
+
},
|
|
18
|
+
icon: {
|
|
19
|
+
type: String,
|
|
20
|
+
default: ""
|
|
21
|
+
},
|
|
22
|
+
disabled: {
|
|
23
|
+
type: Boolean,
|
|
24
|
+
default: false
|
|
25
|
+
},
|
|
26
|
+
exact: {
|
|
27
|
+
type: Boolean,
|
|
28
|
+
default: false
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
const { context: placementContext } = useWebPlacementContext();
|
|
33
|
+
|
|
34
|
+
const resolvedTarget = computed(() => {
|
|
35
|
+
const target = String(props.to || "").trim();
|
|
36
|
+
if (!target) {
|
|
37
|
+
return {
|
|
38
|
+
href: "",
|
|
39
|
+
sameOrigin: true
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const navigationTarget = resolveSurfaceNavigationTargetFromPlacementContext(placementContext.value, {
|
|
44
|
+
path: target
|
|
45
|
+
});
|
|
46
|
+
return {
|
|
47
|
+
href: navigationTarget.href,
|
|
48
|
+
sameOrigin: navigationTarget.sameOrigin
|
|
49
|
+
};
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
const resolvedIcon = computed(() =>
|
|
53
|
+
resolveMenuLinkIcon({
|
|
54
|
+
icon: props.icon,
|
|
55
|
+
label: props.label,
|
|
56
|
+
to: resolvedTarget.value.href || props.to
|
|
57
|
+
})
|
|
58
|
+
);
|
|
59
|
+
</script>
|
|
60
|
+
|
|
61
|
+
<template>
|
|
62
|
+
<v-list-item
|
|
63
|
+
v-if="resolvedTarget.href"
|
|
64
|
+
:title="props.label"
|
|
65
|
+
:to="resolvedTarget.sameOrigin ? resolvedTarget.href : undefined"
|
|
66
|
+
:href="resolvedTarget.sameOrigin ? undefined : resolvedTarget.href"
|
|
67
|
+
:prepend-icon="resolvedIcon || undefined"
|
|
68
|
+
:disabled="props.disabled"
|
|
69
|
+
:exact="props.exact"
|
|
70
|
+
/>
|
|
71
|
+
</template>
|
|
@@ -14,11 +14,7 @@ import {
|
|
|
14
14
|
} from "../placement/surfaceContext.js";
|
|
15
15
|
|
|
16
16
|
const props = defineProps({
|
|
17
|
-
|
|
18
|
-
type: String,
|
|
19
|
-
default: ""
|
|
20
|
-
},
|
|
21
|
-
position: {
|
|
17
|
+
target: {
|
|
22
18
|
type: String,
|
|
23
19
|
default: ""
|
|
24
20
|
},
|
|
@@ -29,6 +25,10 @@ const props = defineProps({
|
|
|
29
25
|
context: {
|
|
30
26
|
type: Object,
|
|
31
27
|
default: () => ({})
|
|
28
|
+
},
|
|
29
|
+
defaultLinkComponentToken: {
|
|
30
|
+
type: String,
|
|
31
|
+
default: ""
|
|
32
32
|
}
|
|
33
33
|
});
|
|
34
34
|
|
|
@@ -78,12 +78,15 @@ const resolvedSurface = computed(() => {
|
|
|
78
78
|
return "*";
|
|
79
79
|
});
|
|
80
80
|
|
|
81
|
+
const resolvedTargetId = computed(() => {
|
|
82
|
+
return String(props.target || "").trim();
|
|
83
|
+
});
|
|
84
|
+
|
|
81
85
|
const placements = computed(() => {
|
|
82
86
|
void revision.value;
|
|
83
87
|
return placementRuntime.getPlacements({
|
|
84
88
|
surface: resolvedSurface.value,
|
|
85
|
-
|
|
86
|
-
position: props.position,
|
|
89
|
+
target: resolvedTargetId.value,
|
|
87
90
|
context: props.context
|
|
88
91
|
});
|
|
89
92
|
});
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { mdiCogOutline } from "@mdi/js";
|
|
3
|
+
import ShellOutlet from "./ShellOutlet.vue";
|
|
4
|
+
|
|
5
|
+
const props = defineProps({
|
|
6
|
+
target: {
|
|
7
|
+
type: String,
|
|
8
|
+
required: true
|
|
9
|
+
},
|
|
10
|
+
defaultLinkComponentToken: {
|
|
11
|
+
type: String,
|
|
12
|
+
default: ""
|
|
13
|
+
},
|
|
14
|
+
icon: {
|
|
15
|
+
type: String,
|
|
16
|
+
default: mdiCogOutline
|
|
17
|
+
},
|
|
18
|
+
ariaLabel: {
|
|
19
|
+
type: String,
|
|
20
|
+
default: "Tools"
|
|
21
|
+
},
|
|
22
|
+
location: {
|
|
23
|
+
type: String,
|
|
24
|
+
default: "bottom end"
|
|
25
|
+
},
|
|
26
|
+
offset: {
|
|
27
|
+
type: [Number, String],
|
|
28
|
+
default: 10
|
|
29
|
+
},
|
|
30
|
+
minWidth: {
|
|
31
|
+
type: [Number, String],
|
|
32
|
+
default: 220
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
</script>
|
|
36
|
+
|
|
37
|
+
<template>
|
|
38
|
+
<v-menu :location="props.location" :offset="props.offset" eager>
|
|
39
|
+
<template #activator="{ props: activatorProps }">
|
|
40
|
+
<v-btn
|
|
41
|
+
v-bind="activatorProps"
|
|
42
|
+
icon
|
|
43
|
+
variant="text"
|
|
44
|
+
:aria-label="props.ariaLabel"
|
|
45
|
+
>
|
|
46
|
+
<v-icon :icon="props.icon" />
|
|
47
|
+
</v-btn>
|
|
48
|
+
</template>
|
|
49
|
+
|
|
50
|
+
<v-list :min-width="props.minWidth" density="comfortable" class="py-1">
|
|
51
|
+
<ShellOutlet
|
|
52
|
+
:target="props.target"
|
|
53
|
+
:default-link-component-token="props.defaultLinkComponentToken"
|
|
54
|
+
/>
|
|
55
|
+
</v-list>
|
|
56
|
+
</v-menu>
|
|
57
|
+
</template>
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { computed } from "vue";
|
|
3
|
+
import { useRoute } from "vue-router";
|
|
4
|
+
import { resolveShellLinkPath } from "../navigation/linkResolver.js";
|
|
5
|
+
import {
|
|
6
|
+
resolveSurfaceIdFromPlacementPathname,
|
|
7
|
+
resolveSurfaceNavigationTargetFromPlacementContext,
|
|
8
|
+
useWebPlacementContext
|
|
9
|
+
} from "../placement/index.js";
|
|
10
|
+
import { resolveMenuLinkIcon } from "../lib/menuIcons.js";
|
|
11
|
+
import { resolveMenuLinkTarget } from "../support/menuLinkTarget.js";
|
|
12
|
+
|
|
13
|
+
const props = defineProps({
|
|
14
|
+
label: {
|
|
15
|
+
type: String,
|
|
16
|
+
default: ""
|
|
17
|
+
},
|
|
18
|
+
to: {
|
|
19
|
+
type: String,
|
|
20
|
+
default: ""
|
|
21
|
+
},
|
|
22
|
+
icon: {
|
|
23
|
+
type: String,
|
|
24
|
+
default: ""
|
|
25
|
+
},
|
|
26
|
+
surface: {
|
|
27
|
+
type: String,
|
|
28
|
+
default: ""
|
|
29
|
+
},
|
|
30
|
+
workspaceSuffix: {
|
|
31
|
+
type: String,
|
|
32
|
+
default: "/"
|
|
33
|
+
},
|
|
34
|
+
nonWorkspaceSuffix: {
|
|
35
|
+
type: String,
|
|
36
|
+
default: "/"
|
|
37
|
+
},
|
|
38
|
+
disabled: {
|
|
39
|
+
type: Boolean,
|
|
40
|
+
default: false
|
|
41
|
+
},
|
|
42
|
+
exact: {
|
|
43
|
+
type: Boolean,
|
|
44
|
+
default: false
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
const route = useRoute();
|
|
49
|
+
const { context: placementContext } = useWebPlacementContext();
|
|
50
|
+
|
|
51
|
+
const currentSurfaceId = computed(() => {
|
|
52
|
+
return resolveSurfaceIdFromPlacementPathname(
|
|
53
|
+
placementContext.value,
|
|
54
|
+
String(route?.path || route?.fullPath || "").trim()
|
|
55
|
+
);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
const resolvedTo = computed(() => {
|
|
59
|
+
return resolveMenuLinkTarget({
|
|
60
|
+
to: props.to,
|
|
61
|
+
surface: props.surface,
|
|
62
|
+
currentSurfaceId: currentSurfaceId.value,
|
|
63
|
+
placementContext: placementContext.value,
|
|
64
|
+
workspaceSuffix: props.workspaceSuffix,
|
|
65
|
+
nonWorkspaceSuffix: props.nonWorkspaceSuffix,
|
|
66
|
+
routeParams: route.params || {},
|
|
67
|
+
resolvePagePath(relativePath, options = {}) {
|
|
68
|
+
return resolveShellLinkPath({
|
|
69
|
+
context: placementContext.value,
|
|
70
|
+
surface: options.surface,
|
|
71
|
+
relativePath,
|
|
72
|
+
params: route.params || {},
|
|
73
|
+
strictParams: options.strictParams !== false
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
const resolvedTarget = computed(() => {
|
|
80
|
+
const target = String(resolvedTo.value || "").trim();
|
|
81
|
+
if (!target) {
|
|
82
|
+
return {
|
|
83
|
+
href: "",
|
|
84
|
+
sameOrigin: true
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const navigationTarget = resolveSurfaceNavigationTargetFromPlacementContext(placementContext.value, {
|
|
89
|
+
path: target
|
|
90
|
+
});
|
|
91
|
+
return {
|
|
92
|
+
href: navigationTarget.href,
|
|
93
|
+
sameOrigin: navigationTarget.sameOrigin
|
|
94
|
+
};
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
const resolvedIcon = computed(() =>
|
|
98
|
+
resolveMenuLinkIcon({
|
|
99
|
+
icon: props.icon,
|
|
100
|
+
label: props.label,
|
|
101
|
+
to: resolvedTarget.value.href || resolvedTo.value
|
|
102
|
+
})
|
|
103
|
+
);
|
|
104
|
+
</script>
|
|
105
|
+
|
|
106
|
+
<template>
|
|
107
|
+
<v-list-item
|
|
108
|
+
v-if="resolvedTarget.href"
|
|
109
|
+
:title="props.label"
|
|
110
|
+
:to="resolvedTarget.sameOrigin ? resolvedTarget.href : undefined"
|
|
111
|
+
:href="resolvedTarget.sameOrigin ? undefined : resolvedTarget.href"
|
|
112
|
+
:prepend-icon="resolvedIcon || undefined"
|
|
113
|
+
:disabled="props.disabled"
|
|
114
|
+
:exact="props.exact"
|
|
115
|
+
/>
|
|
116
|
+
</template>
|