@jskit-ai/shell-web 0.1.18 → 0.1.20

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.
@@ -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.18",
4
+ version: "0.1.20",
5
5
  kind: "runtime",
6
6
  description: "Web shell layout runtime with outlet-based placement contributions.",
7
7
  dependsOn: [],
@@ -87,7 +87,7 @@ export default Object.freeze({
87
87
  dependencies: {
88
88
  runtime: {
89
89
  "@tanstack/vue-query": "^5.90.5",
90
- "@jskit-ai/kernel": "0.1.19",
90
+ "@jskit-ai/kernel": "0.1.21",
91
91
  "vuetify": "^4.0.0"
92
92
  },
93
93
  dev: {}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jskit-ai/shell-web",
3
- "version": "0.1.18",
3
+ "version": "0.1.20",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "test": "node --test"
@@ -13,11 +13,12 @@
13
13
  "./client/components/ShellLayout": "./src/client/components/ShellLayout.vue",
14
14
  "./client/components/ShellOutlet": "./src/client/components/ShellOutlet.vue",
15
15
  "./client/components/ShellErrorHost": "./src/client/components/ShellErrorHost.vue",
16
+ "./client/composables/useShellLayoutState": "./src/client/composables/useShellLayoutState.js",
16
17
  "./client/providers/ShellWebClientProvider": "./src/client/providers/ShellWebClientProvider.js"
17
18
  },
18
19
  "dependencies": {
19
20
  "@tanstack/vue-query": "^5.90.5",
20
- "@jskit-ai/kernel": "0.1.19",
21
+ "@jskit-ai/kernel": "0.1.21",
21
22
  "vuetify": "^4.0.0"
22
23
  }
23
24
  }
@@ -1,14 +1,5 @@
1
1
  <script setup>
2
- import { computed } from "vue";
3
- import { useRoute } from "vue-router";
4
- import { normalizeSurfaceId } from "@jskit-ai/kernel/shared/surface";
5
- import { useWebPlacementContext } from "../placement/inject.js";
6
- import {
7
- readPlacementSurfaceConfig,
8
- resolveSurfaceDefinitionFromPlacementContext,
9
- resolveSurfaceIdFromPlacementPathname
10
- } from "../placement/surfaceContext.js";
11
- import { useShellLayout } from "./useShellLayout.js";
2
+ import { useShellLayoutState } from "../composables/useShellLayoutState.js";
12
3
  import ShellOutlet from "./ShellOutlet.vue";
13
4
 
14
5
  const props = defineProps({
@@ -27,88 +18,10 @@ const props = defineProps({
27
18
  subtitle: {
28
19
  type: String,
29
20
  default: ""
30
- },
31
- topLeftActions: {
32
- type: Array,
33
- default: () => []
34
- },
35
- topRightActions: {
36
- type: Array,
37
- default: () => []
38
- },
39
- menuItems: {
40
- type: Array,
41
- default: () => []
42
- }
43
- });
44
-
45
- let route = null;
46
- try {
47
- route = useRoute();
48
- } catch {
49
- route = null;
50
- }
51
- const { context: placementContext } = useWebPlacementContext();
52
-
53
- function toSurfaceLabel(surfaceId = "") {
54
- const normalizedSurfaceId = String(surfaceId || "").trim().toLowerCase();
55
- if (!normalizedSurfaceId) {
56
- return "Surface";
57
- }
58
-
59
- return normalizedSurfaceId
60
- .split(/[^a-z0-9]+/g)
61
- .filter(Boolean)
62
- .map((segment) => `${segment.slice(0, 1).toUpperCase()}${segment.slice(1)}`)
63
- .join(" ");
64
- }
65
-
66
- const resolvedSurface = computed(() => {
67
- const explicitSurface = normalizeSurfaceId(props.surface);
68
- if (explicitSurface) {
69
- return explicitSurface;
70
21
  }
71
-
72
- const pathname =
73
- String(route?.path || "").trim() ||
74
- (typeof window === "object" && window?.location?.pathname ? String(window.location.pathname).trim() : "/");
75
- const contextValue = placementContext?.value || null;
76
- const resolvedSurfaceFromPath = resolveSurfaceIdFromPlacementPathname(contextValue, pathname);
77
- if (resolvedSurfaceFromPath) {
78
- return resolvedSurfaceFromPath;
79
- }
80
-
81
- const surfaceConfig = readPlacementSurfaceConfig(contextValue);
82
- if (surfaceConfig.defaultSurfaceId) {
83
- return surfaceConfig.defaultSurfaceId;
84
- }
85
-
86
- return "surface";
87
- });
88
-
89
- const resolvedSurfaceLabel = computed(() => {
90
- const explicitLabel = String(props.surfaceLabel || "").trim();
91
- if (explicitLabel) {
92
- return explicitLabel;
93
- }
94
-
95
- const surfaceDefinition = resolveSurfaceDefinitionFromPlacementContext(
96
- placementContext?.value || null,
97
- resolvedSurface.value
98
- );
99
- const configuredLabel = String(surfaceDefinition?.label || "").trim();
100
- if (configuredLabel) {
101
- return configuredLabel;
102
- }
103
-
104
- return toSurfaceLabel(resolvedSurface.value);
105
22
  });
106
23
 
107
- const { drawerOpen, resolvedTopLeftActions, resolvedTopRightActions, resolvedMenuItems, toggleDrawer } = useShellLayout({
108
- topLeftActions: computed(() => props.topLeftActions),
109
- topRightActions: computed(() => props.topRightActions),
110
- menuItems: computed(() => props.menuItems)
111
- });
24
+ const { drawerOpen, toggleDrawer, resolvedSurface, resolvedSurfaceLabel } = useShellLayoutState(props);
112
25
  </script>
113
26
 
114
27
  <template>
@@ -116,19 +29,8 @@ const { drawerOpen, resolvedTopLeftActions, resolvedTopRightActions, resolvedMen
116
29
  <v-app-bar border density="comfortable" elevation="0" class="bg-surface">
117
30
  <v-app-bar-nav-icon aria-label="Toggle navigation menu" @click="toggleDrawer" />
118
31
 
119
- <slot name="top-left" :actions="resolvedTopLeftActions" :surface="resolvedSurface">
32
+ <slot name="top-left" :surface="resolvedSurface">
120
33
  <div class="d-flex align-center ga-2">
121
- <v-btn
122
- v-for="action in resolvedTopLeftActions"
123
- :key="`top-left-${action.label}`"
124
- :to="action.to || undefined"
125
- :variant="action.variant"
126
- :color="action.color"
127
- size="small"
128
- class="text-none"
129
- >
130
- {{ action.label }}
131
- </v-btn>
132
34
  <v-chip color="primary" size="small" label>{{ resolvedSurfaceLabel }}</v-chip>
133
35
  <ShellOutlet host="shell-layout" position="top-left" />
134
36
  </div>
@@ -136,38 +38,18 @@ const { drawerOpen, resolvedTopLeftActions, resolvedTopRightActions, resolvedMen
136
38
 
137
39
  <v-spacer />
138
40
 
139
- <slot name="top-right" :actions="resolvedTopRightActions" :surface="resolvedSurface">
41
+ <slot name="top-right" :surface="resolvedSurface">
140
42
  <div class="d-flex align-center ga-2">
141
- <v-btn
142
- v-for="action in resolvedTopRightActions"
143
- :key="`top-right-${action.label}`"
144
- :to="action.to || undefined"
145
- :variant="action.variant"
146
- :color="action.color"
147
- size="small"
148
- class="text-none"
149
- >
150
- {{ action.label }}
151
- </v-btn>
152
43
  <ShellOutlet host="shell-layout" position="top-right" />
153
44
  </div>
154
45
  </slot>
155
46
  </v-app-bar>
156
47
 
157
48
  <v-navigation-drawer v-model="drawerOpen" border class="bg-surface" :width="248">
158
- <slot name="menu" :items="resolvedMenuItems" :surface="resolvedSurface">
49
+ <slot name="menu" :surface="resolvedSurface">
159
50
  <v-list nav density="comfortable" class="pt-2">
160
51
  <v-list-subheader class="text-uppercase text-caption">{{ resolvedSurfaceLabel }}</v-list-subheader>
161
- <v-list-item
162
- v-for="item in resolvedMenuItems"
163
- :key="`menu-${item.label}`"
164
- :title="item.label"
165
- :to="item.to"
166
- :prepend-icon="item.icon"
167
- rounded="lg"
168
- class="mb-1"
169
- />
170
- <ShellOutlet host="shell-layout" position="primary-menu" />
52
+ <ShellOutlet host="shell-layout" position="primary-menu" default />
171
53
  <v-divider class="my-2" />
172
54
  <ShellOutlet host="shell-layout" position="secondary-menu" />
173
55
  </v-list>
@@ -22,6 +22,10 @@ const props = defineProps({
22
22
  type: String,
23
23
  default: ""
24
24
  },
25
+ default: {
26
+ type: Boolean,
27
+ default: false
28
+ },
25
29
  context: {
26
30
  type: Object,
27
31
  default: () => ({})
@@ -0,0 +1,88 @@
1
+ import { computed, ref } from "vue";
2
+ import { useRoute } from "vue-router";
3
+ import { normalizeSurfaceId } from "@jskit-ai/kernel/shared/surface";
4
+ import { useWebPlacementContext } from "../placement/inject.js";
5
+ import {
6
+ readPlacementSurfaceConfig,
7
+ resolveSurfaceDefinitionFromPlacementContext,
8
+ resolveSurfaceIdFromPlacementPathname
9
+ } from "../placement/surfaceContext.js";
10
+
11
+ function toSurfaceLabel(surfaceId = "") {
12
+ const normalizedSurfaceId = String(surfaceId || "").trim().toLowerCase();
13
+ if (!normalizedSurfaceId) {
14
+ return "Surface";
15
+ }
16
+
17
+ return normalizedSurfaceId
18
+ .split(/[^a-z0-9]+/g)
19
+ .filter(Boolean)
20
+ .map((segment) => `${segment.slice(0, 1).toUpperCase()}${segment.slice(1)}`)
21
+ .join(" ");
22
+ }
23
+
24
+ function useShellLayoutState(props = {}) {
25
+ let route = null;
26
+ try {
27
+ route = useRoute();
28
+ } catch {
29
+ route = null;
30
+ }
31
+
32
+ const { context: placementContext } = useWebPlacementContext();
33
+ const drawerOpen = ref(true);
34
+
35
+ function toggleDrawer() {
36
+ drawerOpen.value = !drawerOpen.value;
37
+ }
38
+
39
+ const resolvedSurface = computed(() => {
40
+ const explicitSurface = normalizeSurfaceId(props?.surface);
41
+ if (explicitSurface) {
42
+ return explicitSurface;
43
+ }
44
+
45
+ const pathname =
46
+ String(route?.path || "").trim() ||
47
+ (typeof window === "object" && window?.location?.pathname ? String(window.location.pathname).trim() : "/");
48
+ const contextValue = placementContext?.value || null;
49
+ const resolvedSurfaceFromPath = resolveSurfaceIdFromPlacementPathname(contextValue, pathname);
50
+ if (resolvedSurfaceFromPath) {
51
+ return resolvedSurfaceFromPath;
52
+ }
53
+
54
+ const surfaceConfig = readPlacementSurfaceConfig(contextValue);
55
+ if (surfaceConfig.defaultSurfaceId) {
56
+ return surfaceConfig.defaultSurfaceId;
57
+ }
58
+
59
+ return "surface";
60
+ });
61
+
62
+ const resolvedSurfaceLabel = computed(() => {
63
+ const explicitLabel = String(props?.surfaceLabel || "").trim();
64
+ if (explicitLabel) {
65
+ return explicitLabel;
66
+ }
67
+
68
+ const surfaceDefinition = resolveSurfaceDefinitionFromPlacementContext(
69
+ placementContext?.value || null,
70
+ resolvedSurface.value
71
+ );
72
+ const configuredLabel = String(surfaceDefinition?.label || "").trim();
73
+ if (configuredLabel) {
74
+ return configuredLabel;
75
+ }
76
+
77
+ return toSurfaceLabel(resolvedSurface.value);
78
+ });
79
+
80
+ return Object.freeze({
81
+ drawerOpen,
82
+ toggleDrawer,
83
+ resolvedSurface,
84
+ resolvedSurfaceLabel
85
+ });
86
+ }
87
+
88
+ export { useShellLayoutState };
@@ -9,6 +9,7 @@ export {
9
9
  export { default as ShellLayout } from "./components/ShellLayout.vue";
10
10
  export { default as ShellOutlet } from "./components/ShellOutlet.vue";
11
11
  export { default as ShellErrorHost } from "./components/ShellErrorHost.vue";
12
+ export { useShellLayoutState } from "./composables/useShellLayoutState.js";
12
13
 
13
14
  const clientProviders = Object.freeze([ShellWebClientProvider]);
14
15
 
@@ -1,29 +1,7 @@
1
1
  <script setup>
2
- import { computed, ref } from "vue";
3
- import { useRoute } from "vue-router";
4
- import { normalizeObject } from "@jskit-ai/kernel/shared/support/normalize";
5
- import { normalizeSurfaceId } from "@jskit-ai/kernel/shared/surface";
6
- import {
7
- useWebPlacementContext,
8
- readPlacementSurfaceConfig,
9
- resolveSurfaceDefinitionFromPlacementContext,
10
- resolveSurfaceIdFromPlacementPathname
11
- } from "@jskit-ai/shell-web/client/placement";
2
+ import { useShellLayoutState } from "@jskit-ai/shell-web/client/composables/useShellLayoutState";
12
3
  import ShellOutlet from "@jskit-ai/shell-web/client/components/ShellOutlet";
13
4
 
14
- const DEFAULT_ACTION_FALLBACK = Object.freeze({
15
- label: "",
16
- to: "",
17
- variant: "text",
18
- color: "secondary"
19
- });
20
-
21
- const DEFAULT_MENU_FALLBACK = Object.freeze({
22
- label: "",
23
- to: "/",
24
- icon: "$menu"
25
- });
26
-
27
5
  const props = defineProps({
28
6
  surface: {
29
7
  type: String,
@@ -40,131 +18,10 @@ const props = defineProps({
40
18
  subtitle: {
41
19
  type: String,
42
20
  default: ""
43
- },
44
- topLeftActions: {
45
- type: Array,
46
- default: () => []
47
- },
48
- topRightActions: {
49
- type: Array,
50
- default: () => []
51
- },
52
- menuItems: {
53
- type: Array,
54
- default: () => []
55
- }
56
- });
57
-
58
- const route = useRoute();
59
- const { context: placementContext } = useWebPlacementContext();
60
- const drawerOpen = ref(true);
61
-
62
- function normalizeAction(action, fallback) {
63
- const source = normalizeObject(action);
64
- const fallbackSource = normalizeObject(fallback);
65
- const label = String(source.label || fallbackSource.label || "").trim();
66
- if (!label) {
67
- return null;
68
- }
69
-
70
- return {
71
- label,
72
- to: String(source.to || fallbackSource.to || "").trim(),
73
- variant: String(source.variant || fallbackSource.variant || "text").trim(),
74
- color: String(source.color || fallbackSource.color || "secondary").trim()
75
- };
76
- }
77
-
78
- function normalizeMenuItem(item, fallback) {
79
- const source = normalizeObject(item);
80
- const fallbackSource = normalizeObject(fallback);
81
- const label = String(source.label || fallbackSource.label || "").trim();
82
- if (!label) {
83
- return null;
84
- }
85
-
86
- return {
87
- label,
88
- to: String(source.to || fallbackSource.to || "").trim() || "/",
89
- icon: String(source.icon || fallbackSource.icon || "$menu").trim() || "$menu"
90
- };
91
- }
92
-
93
- function normalizeActionList(actions) {
94
- const source = Array.isArray(actions) ? actions : [];
95
- return source
96
- .map((item) => normalizeAction(item, DEFAULT_ACTION_FALLBACK))
97
- .filter(Boolean);
98
- }
99
-
100
- function normalizeMenuList(items) {
101
- const source = Array.isArray(items) ? items : [];
102
- return source
103
- .map((item) => normalizeMenuItem(item, DEFAULT_MENU_FALLBACK))
104
- .filter(Boolean);
105
- }
106
-
107
- function toSurfaceLabel(surfaceId = "") {
108
- const normalizedSurfaceId = String(surfaceId || "").trim().toLowerCase();
109
- if (!normalizedSurfaceId) {
110
- return "Surface";
111
- }
112
-
113
- return normalizedSurfaceId
114
- .split(/[^a-z0-9]+/g)
115
- .filter(Boolean)
116
- .map((segment) => `${segment.slice(0, 1).toUpperCase()}${segment.slice(1)}`)
117
- .join(" ");
118
- }
119
-
120
- function toggleDrawer() {
121
- drawerOpen.value = !drawerOpen.value;
122
- }
123
-
124
- const resolvedSurface = computed(() => {
125
- const explicitSurface = normalizeSurfaceId(props.surface);
126
- if (explicitSurface) {
127
- return explicitSurface;
128
- }
129
-
130
- const pathname =
131
- String(route?.path || "").trim() ||
132
- (typeof window === "object" && window?.location?.pathname ? String(window.location.pathname).trim() : "/");
133
- const contextValue = placementContext?.value || null;
134
- const resolvedSurfaceFromPath = resolveSurfaceIdFromPlacementPathname(contextValue, pathname);
135
- if (resolvedSurfaceFromPath) {
136
- return resolvedSurfaceFromPath;
137
- }
138
-
139
- const surfaceConfig = readPlacementSurfaceConfig(contextValue);
140
- if (surfaceConfig.defaultSurfaceId) {
141
- return surfaceConfig.defaultSurfaceId;
142
- }
143
-
144
- return "surface";
145
- });
146
-
147
- const resolvedSurfaceLabel = computed(() => {
148
- const explicitLabel = String(props.surfaceLabel || "").trim();
149
- if (explicitLabel) {
150
- return explicitLabel;
151
21
  }
152
-
153
- const surfaceDefinition = resolveSurfaceDefinitionFromPlacementContext(
154
- placementContext?.value || null,
155
- resolvedSurface.value
156
- );
157
- const configuredLabel = String(surfaceDefinition?.label || "").trim();
158
- if (configuredLabel) {
159
- return configuredLabel;
160
- }
161
-
162
- return toSurfaceLabel(resolvedSurface.value);
163
22
  });
164
23
 
165
- const resolvedTopLeftActions = computed(() => normalizeActionList(props.topLeftActions));
166
- const resolvedTopRightActions = computed(() => normalizeActionList(props.topRightActions));
167
- const resolvedMenuItems = computed(() => normalizeMenuList(props.menuItems));
24
+ const { drawerOpen, toggleDrawer, resolvedSurface, resolvedSurfaceLabel } = useShellLayoutState(props);
168
25
  </script>
169
26
 
170
27
  <template>
@@ -172,19 +29,8 @@ const resolvedMenuItems = computed(() => normalizeMenuList(props.menuItems));
172
29
  <v-app-bar border density="comfortable" elevation="0" class="bg-surface">
173
30
  <v-app-bar-nav-icon aria-label="Toggle navigation menu" @click="toggleDrawer" />
174
31
 
175
- <slot name="top-left" :actions="resolvedTopLeftActions" :surface="resolvedSurface">
32
+ <slot name="top-left" :surface="resolvedSurface">
176
33
  <div class="d-flex align-center ga-2">
177
- <v-btn
178
- v-for="action in resolvedTopLeftActions"
179
- :key="`top-left-${action.label}`"
180
- :to="action.to || undefined"
181
- :variant="action.variant"
182
- :color="action.color"
183
- size="small"
184
- class="text-none"
185
- >
186
- {{ action.label }}
187
- </v-btn>
188
34
  <v-chip color="primary" size="small" label>{{ resolvedSurfaceLabel }}</v-chip>
189
35
  <ShellOutlet host="shell-layout" position="top-left" />
190
36
  </div>
@@ -192,38 +38,18 @@ const resolvedMenuItems = computed(() => normalizeMenuList(props.menuItems));
192
38
 
193
39
  <v-spacer />
194
40
 
195
- <slot name="top-right" :actions="resolvedTopRightActions" :surface="resolvedSurface">
41
+ <slot name="top-right" :surface="resolvedSurface">
196
42
  <div class="d-flex align-center ga-2">
197
- <v-btn
198
- v-for="action in resolvedTopRightActions"
199
- :key="`top-right-${action.label}`"
200
- :to="action.to || undefined"
201
- :variant="action.variant"
202
- :color="action.color"
203
- size="small"
204
- class="text-none"
205
- >
206
- {{ action.label }}
207
- </v-btn>
208
43
  <ShellOutlet host="shell-layout" position="top-right" />
209
44
  </div>
210
45
  </slot>
211
46
  </v-app-bar>
212
47
 
213
48
  <v-navigation-drawer v-model="drawerOpen" border class="bg-surface" :width="248">
214
- <slot name="menu" :items="resolvedMenuItems" :surface="resolvedSurface">
49
+ <slot name="menu" :surface="resolvedSurface">
215
50
  <v-list nav density="comfortable" class="pt-2">
216
51
  <v-list-subheader class="text-uppercase text-caption">{{ resolvedSurfaceLabel }}</v-list-subheader>
217
- <v-list-item
218
- v-for="item in resolvedMenuItems"
219
- :key="`menu-${item.label}`"
220
- :title="item.label"
221
- :to="item.to"
222
- :prepend-icon="item.icon"
223
- rounded="lg"
224
- class="mb-1"
225
- />
226
- <ShellOutlet host="shell-layout" position="primary-menu" />
52
+ <ShellOutlet host="shell-layout" position="primary-menu" default />
227
53
  <v-divider class="my-2" />
228
54
  <ShellOutlet host="shell-layout" position="secondary-menu" />
229
55
  </v-list>
@@ -1,102 +0,0 @@
1
- import { computed, ref } from "vue";
2
- import { normalizeObject } from "@jskit-ai/kernel/shared/support/normalize";
3
-
4
- const DEFAULT_ACTION_FALLBACK = Object.freeze({
5
- label: "",
6
- to: "",
7
- variant: "text",
8
- color: "secondary"
9
- });
10
-
11
- const DEFAULT_MENU_FALLBACK = Object.freeze({
12
- label: "",
13
- to: "/",
14
- icon: "$menu"
15
- });
16
-
17
- function normalizeLabeledItem(item, fallback, buildItem) {
18
- const source = normalizeObject(item);
19
- const fallbackSource = normalizeObject(fallback);
20
- const label = String(source.label || fallbackSource.label || "").trim();
21
- if (!label) {
22
- return null;
23
- }
24
-
25
- if (typeof buildItem !== "function") {
26
- return null;
27
- }
28
-
29
- return buildItem({
30
- source,
31
- fallbackSource,
32
- label
33
- });
34
- }
35
-
36
- function normalizeAction(action, fallback) {
37
- return normalizeLabeledItem(action, fallback, ({ source, fallbackSource, label }) => {
38
- return {
39
- label,
40
- to: String(source.to || fallbackSource.to || "").trim(),
41
- variant: String(source.variant || fallbackSource.variant || "text").trim(),
42
- color: String(source.color || fallbackSource.color || "secondary").trim()
43
- };
44
- });
45
- }
46
-
47
- function normalizeMenuItem(item, fallback) {
48
- return normalizeLabeledItem(item, fallback, ({ source, fallbackSource, label }) => {
49
- return {
50
- label,
51
- to: String(source.to || fallbackSource.to || "").trim() || "/",
52
- icon: String(source.icon || fallbackSource.icon || "$menu").trim() || "$menu"
53
- };
54
- });
55
- }
56
-
57
- function normalizeActionList(actions) {
58
- const sourceActions = Array.isArray(actions) ? actions : [];
59
- return sourceActions
60
- .map((item) => normalizeAction(item, DEFAULT_ACTION_FALLBACK))
61
- .filter(Boolean);
62
- }
63
-
64
- function normalizeMenuList(items) {
65
- const source = Array.isArray(items) ? items : [];
66
- return source
67
- .map((item) => normalizeMenuItem(item, DEFAULT_MENU_FALLBACK))
68
- .filter(Boolean);
69
- }
70
-
71
- function useShellLayout({ topLeftActions, topRightActions, menuItems } = {}) {
72
- const drawerOpen = ref(true);
73
-
74
- const resolvedTopLeftActions = computed(() => {
75
- const source = topLeftActions?.value;
76
- return normalizeActionList(source);
77
- });
78
-
79
- const resolvedTopRightActions = computed(() => {
80
- const source = topRightActions?.value;
81
- return normalizeActionList(source);
82
- });
83
-
84
- const resolvedMenuItems = computed(() => {
85
- const source = menuItems?.value;
86
- return normalizeMenuList(source);
87
- });
88
-
89
- function toggleDrawer() {
90
- drawerOpen.value = !drawerOpen.value;
91
- }
92
-
93
- return {
94
- drawerOpen,
95
- resolvedTopLeftActions,
96
- resolvedTopRightActions,
97
- resolvedMenuItems,
98
- toggleDrawer
99
- };
100
- }
101
-
102
- export { useShellLayout };