@jskit-ai/shell-web 0.1.31 → 0.1.33
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 +114 -15
- 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/console/index.vue +12 -0
- package/templates/expected-existing/src/pages/console.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 +88 -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/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.33",
|
|
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.34",
|
|
97
96
|
"vuetify": "^4.0.0"
|
|
98
97
|
},
|
|
99
98
|
dev: {}
|
|
@@ -106,11 +105,74 @@ export default Object.freeze({
|
|
|
106
105
|
}
|
|
107
106
|
},
|
|
108
107
|
procfile: {},
|
|
109
|
-
text: [
|
|
108
|
+
text: [
|
|
109
|
+
{
|
|
110
|
+
op: "append-text",
|
|
111
|
+
file: "packages/main/src/client/providers/MainClientProvider.js",
|
|
112
|
+
position: "top",
|
|
113
|
+
skipIfContains: "import MenuLinkItem from \"/src/components/menus/MenuLinkItem.vue\";",
|
|
114
|
+
value: "import MenuLinkItem from \"/src/components/menus/MenuLinkItem.vue\";\n",
|
|
115
|
+
reason: "Bind app-owned shell menu link-item scaffold into local main client provider imports.",
|
|
116
|
+
category: "shell-web",
|
|
117
|
+
id: "shell-web-main-client-provider-menu-link-item-import"
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
op: "append-text",
|
|
121
|
+
file: "packages/main/src/client/providers/MainClientProvider.js",
|
|
122
|
+
position: "top",
|
|
123
|
+
skipIfContains: "import SurfaceAwareMenuLinkItem from \"/src/components/menus/SurfaceAwareMenuLinkItem.vue\";",
|
|
124
|
+
value: "import SurfaceAwareMenuLinkItem from \"/src/components/menus/SurfaceAwareMenuLinkItem.vue\";\n",
|
|
125
|
+
reason: "Bind app-owned shell surface-aware menu link-item scaffold into local main client provider imports.",
|
|
126
|
+
category: "shell-web",
|
|
127
|
+
id: "shell-web-main-client-provider-surface-aware-menu-link-item-import"
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
op: "append-text",
|
|
131
|
+
file: "packages/main/src/client/providers/MainClientProvider.js",
|
|
132
|
+
position: "top",
|
|
133
|
+
skipIfContains: "import TabLinkItem from \"/src/components/menus/TabLinkItem.vue\";",
|
|
134
|
+
value: "import TabLinkItem from \"/src/components/menus/TabLinkItem.vue\";\n",
|
|
135
|
+
reason: "Bind app-owned shell tab link-item scaffold into local main client provider imports.",
|
|
136
|
+
category: "shell-web",
|
|
137
|
+
id: "shell-web-main-client-provider-tab-link-item-import"
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
op: "append-text",
|
|
141
|
+
file: "packages/main/src/client/providers/MainClientProvider.js",
|
|
142
|
+
position: "bottom",
|
|
143
|
+
skipIfContains: "registerMainClientComponent(\"local.main.ui.menu-link-item\", () => MenuLinkItem);",
|
|
144
|
+
value: "\nregisterMainClientComponent(\"local.main.ui.menu-link-item\", () => MenuLinkItem);\n",
|
|
145
|
+
reason: "Bind app-owned shell menu link-item token into local main client provider registry.",
|
|
146
|
+
category: "shell-web",
|
|
147
|
+
id: "shell-web-main-client-provider-menu-link-item-register"
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
op: "append-text",
|
|
151
|
+
file: "packages/main/src/client/providers/MainClientProvider.js",
|
|
152
|
+
position: "bottom",
|
|
153
|
+
skipIfContains: "registerMainClientComponent(\"local.main.ui.surface-aware-menu-link-item\", () => SurfaceAwareMenuLinkItem);",
|
|
154
|
+
value: "\nregisterMainClientComponent(\"local.main.ui.surface-aware-menu-link-item\", () => SurfaceAwareMenuLinkItem);\n",
|
|
155
|
+
reason: "Bind app-owned shell surface-aware menu link-item token into local main client provider registry.",
|
|
156
|
+
category: "shell-web",
|
|
157
|
+
id: "shell-web-main-client-provider-surface-aware-menu-link-item-register"
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
op: "append-text",
|
|
161
|
+
file: "packages/main/src/client/providers/MainClientProvider.js",
|
|
162
|
+
position: "bottom",
|
|
163
|
+
skipIfContains: "registerMainClientComponent(\"local.main.ui.tab-link-item\", () => TabLinkItem);",
|
|
164
|
+
value: "\nregisterMainClientComponent(\"local.main.ui.tab-link-item\", () => TabLinkItem);\n",
|
|
165
|
+
reason: "Bind app-owned shell tab link-item token into local main client provider registry.",
|
|
166
|
+
category: "shell-web",
|
|
167
|
+
id: "shell-web-main-client-provider-tab-link-item-register"
|
|
168
|
+
}
|
|
169
|
+
],
|
|
110
170
|
files: [
|
|
111
171
|
{
|
|
112
172
|
from: "templates/src/App.vue",
|
|
113
173
|
to: "src/App.vue",
|
|
174
|
+
ownership: "app",
|
|
175
|
+
expectedExistingFrom: "templates/expected-existing/src/App.vue",
|
|
114
176
|
reason: "Install full-width shell app root with shell-web error host and edge-to-edge layout.",
|
|
115
177
|
category: "shell-web",
|
|
116
178
|
id: "shell-web-app-root"
|
|
@@ -118,13 +180,39 @@ export default Object.freeze({
|
|
|
118
180
|
{
|
|
119
181
|
from: "templates/src/components/ShellLayout.vue",
|
|
120
182
|
to: "src/components/ShellLayout.vue",
|
|
183
|
+
ownership: "app",
|
|
121
184
|
reason: "Install app-owned shell layout component so apps can customize structure and slots.",
|
|
122
185
|
category: "shell-web",
|
|
123
186
|
id: "shell-web-component-shell-layout"
|
|
124
187
|
},
|
|
188
|
+
{
|
|
189
|
+
from: "templates/src/components/menus/MenuLinkItem.vue",
|
|
190
|
+
to: "src/components/menus/MenuLinkItem.vue",
|
|
191
|
+
ownership: "app",
|
|
192
|
+
reason: "Install app-owned shell menu link-item scaffold for local placement customization.",
|
|
193
|
+
category: "shell-web",
|
|
194
|
+
id: "shell-web-component-menu-link-item"
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
from: "templates/src/components/menus/SurfaceAwareMenuLinkItem.vue",
|
|
198
|
+
to: "src/components/menus/SurfaceAwareMenuLinkItem.vue",
|
|
199
|
+
ownership: "app",
|
|
200
|
+
reason: "Install app-owned surface-aware shell menu link-item scaffold for local placement customization.",
|
|
201
|
+
category: "shell-web",
|
|
202
|
+
id: "shell-web-component-surface-aware-menu-link-item"
|
|
203
|
+
},
|
|
204
|
+
{
|
|
205
|
+
from: "templates/src/components/menus/TabLinkItem.vue",
|
|
206
|
+
to: "src/components/menus/TabLinkItem.vue",
|
|
207
|
+
ownership: "app",
|
|
208
|
+
reason: "Install app-owned shell tab link-item scaffold for local placement customization.",
|
|
209
|
+
category: "shell-web",
|
|
210
|
+
id: "shell-web-component-tab-link-item"
|
|
211
|
+
},
|
|
125
212
|
{
|
|
126
213
|
from: "templates/src/error.js",
|
|
127
214
|
to: "src/error.js",
|
|
215
|
+
ownership: "app",
|
|
128
216
|
reason: "Install app-owned error runtime policy and presenter config scaffold.",
|
|
129
217
|
category: "shell-web",
|
|
130
218
|
id: "shell-web-error-config"
|
|
@@ -132,6 +220,7 @@ export default Object.freeze({
|
|
|
132
220
|
{
|
|
133
221
|
from: "templates/src/placement.js",
|
|
134
222
|
to: "src/placement.js",
|
|
223
|
+
ownership: "app",
|
|
135
224
|
reason: "Install app-owned placement registry scaffold used by shell-web placement runtime.",
|
|
136
225
|
category: "shell-web",
|
|
137
226
|
id: "shell-web-placement-registry"
|
|
@@ -140,6 +229,8 @@ export default Object.freeze({
|
|
|
140
229
|
from: "templates/src/pages/home.vue",
|
|
141
230
|
toSurface: "home",
|
|
142
231
|
toSurfaceRoot: true,
|
|
232
|
+
ownership: "app",
|
|
233
|
+
expectedExistingFrom: "templates/expected-existing/src/pages/home.vue",
|
|
143
234
|
reason: "Install shell-driven home wrapper page.",
|
|
144
235
|
category: "shell-web",
|
|
145
236
|
id: "shell-web-page-home-wrapper"
|
|
@@ -148,6 +239,8 @@ export default Object.freeze({
|
|
|
148
239
|
from: "templates/src/pages/home/index.vue",
|
|
149
240
|
toSurface: "home",
|
|
150
241
|
toSurfacePath: "index.vue",
|
|
242
|
+
ownership: "app",
|
|
243
|
+
expectedExistingFrom: "templates/expected-existing/src/pages/home/index.vue",
|
|
151
244
|
reason: "Install shell-driven home surface starter page.",
|
|
152
245
|
category: "shell-web",
|
|
153
246
|
id: "shell-web-page-home"
|
|
@@ -156,6 +249,7 @@ export default Object.freeze({
|
|
|
156
249
|
from: "templates/src/pages/home/settings.vue",
|
|
157
250
|
toSurface: "home",
|
|
158
251
|
toSurfacePath: "settings.vue",
|
|
252
|
+
ownership: "app",
|
|
159
253
|
reason: "Install shell-driven home settings shell route with section navigation.",
|
|
160
254
|
category: "shell-web",
|
|
161
255
|
id: "shell-web-page-home-settings-shell"
|
|
@@ -164,7 +258,8 @@ export default Object.freeze({
|
|
|
164
258
|
from: "templates/src/pages/home/settings/index.vue",
|
|
165
259
|
toSurface: "home",
|
|
166
260
|
toSurfacePath: "settings/index.vue",
|
|
167
|
-
|
|
261
|
+
ownership: "app",
|
|
262
|
+
reason: "Install shell-driven home settings index stub scaffold for app-owned landing or redirect behavior.",
|
|
168
263
|
category: "shell-web",
|
|
169
264
|
id: "shell-web-page-home-settings"
|
|
170
265
|
},
|
|
@@ -172,6 +267,8 @@ export default Object.freeze({
|
|
|
172
267
|
from: "templates/src/pages/console.vue",
|
|
173
268
|
toSurface: "console",
|
|
174
269
|
toSurfaceRoot: true,
|
|
270
|
+
ownership: "app",
|
|
271
|
+
expectedExistingFrom: "templates/expected-existing/src/pages/console.vue",
|
|
175
272
|
reason: "Install shell-driven console wrapper page.",
|
|
176
273
|
category: "shell-web",
|
|
177
274
|
id: "shell-web-page-console-wrapper"
|
|
@@ -180,6 +277,8 @@ export default Object.freeze({
|
|
|
180
277
|
from: "templates/src/pages/console/index.vue",
|
|
181
278
|
toSurface: "console",
|
|
182
279
|
toSurfacePath: "index.vue",
|
|
280
|
+
ownership: "app",
|
|
281
|
+
expectedExistingFrom: "templates/expected-existing/src/pages/console/index.vue",
|
|
183
282
|
reason: "Install shell-driven console page starter.",
|
|
184
283
|
category: "shell-web",
|
|
185
284
|
id: "shell-web-page-console"
|
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.33",
|
|
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.34",
|
|
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>
|