@jskit-ai/shell-web 0.1.149 → 0.1.151
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/CHANGELOG.md +14 -0
- package/fixtures/adaptive-shell/index.html +12 -0
- package/fixtures/adaptive-shell/src/App.vue +34 -0
- package/fixtures/adaptive-shell/src/ScreenPage.vue +14 -0
- package/fixtures/adaptive-shell/src/main.js +197 -0
- package/fixtures/adaptive-shell/vite.config.mjs +17 -0
- package/package.descriptor.mjs +2 -2
- package/package.json +2 -2
- package/src/client/components/ShellLayout.vue +258 -16
- package/src/client/components/ShellMenuLinkItem.vue +18 -12
- package/src/client/components/ShellNavigationTooltip.vue +37 -0
- package/src/client/components/ShellSurfaceAwareMenuLinkItem.vue +18 -12
- package/src/client/support/drawerWidth.js +91 -0
- package/src/client/support/navigationLinkKeyboard.js +10 -0
- package/src/test/adaptiveShellSmoke.js +192 -16
- package/templates/src/components/ShellLayout.vue +1 -0
- package/test/adaptiveShell.browser.test.js +324 -0
- package/test/drawerWidth.test.js +59 -0
- package/test/linkItemScaffoldContract.test.js +18 -2
- package/test/navigationLinkKeyboard.test.js +36 -0
- package/test/playwrightContract.test.js +17 -0
- package/test/settingsPlacementContract.test.js +16 -2
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.1.151 - 2026-08-13
|
|
4
|
+
|
|
5
|
+
- Replace Vuetify's oversized default drawer icon spacer with one public `navigationItemSpacing` value, defaulting to 12px for both icon-to-label and widest-label-to-edge spacing.
|
|
6
|
+
- Keep the Material 3 80px default rail while documenting `railWidth` as the supported product-density override.
|
|
7
|
+
|
|
8
|
+
## 0.1.150 - 2026-08-13
|
|
9
|
+
|
|
10
|
+
- Give shell navigation tooltips one opaque, theme-owned Material color pair on hover and keyboard focus.
|
|
11
|
+
- Centre complete 48px navigation targets and their selected-state indicators in the collapsed desktop rail.
|
|
12
|
+
- Size the open drawer from its widest visible label with an approximately 10px end gap, while exposing `drawerWidth` and `railWidth` overrides.
|
|
13
|
+
- Dismiss compact navigation with the scrim or Escape and restore focus to the navigation toggle.
|
|
14
|
+
- Make the exported adaptive-shell Playwright helper follow rendered breakpoints and wait for stable drawer transitions and geometry.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>JSKIT shell-web browser contract</title>
|
|
7
|
+
</head>
|
|
8
|
+
<body>
|
|
9
|
+
<div id="app"></div>
|
|
10
|
+
<script type="module" src="/src/main.js"></script>
|
|
11
|
+
</body>
|
|
12
|
+
</html>
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { computed, watchEffect } from "vue";
|
|
3
|
+
import { RouterView, useRoute } from "vue-router";
|
|
4
|
+
import { useTheme } from "vuetify";
|
|
5
|
+
import ShellLayout from "@jskit-ai/shell-web/client/components/ShellLayout";
|
|
6
|
+
|
|
7
|
+
const route = useRoute();
|
|
8
|
+
const theme = useTheme();
|
|
9
|
+
const surface = computed(() => route.path.startsWith("/w/") ? "admin" : "home");
|
|
10
|
+
const surfaceLabel = computed(() => surface.value === "admin" ? "Admin" : "Home");
|
|
11
|
+
const themeMode = computed(() => route.query.theme === "dark" ? "dark" : "light");
|
|
12
|
+
const railWidth = computed(() => Number(route.query.railWidth) || undefined);
|
|
13
|
+
const navigationItemSpacing = computed(() => Number(route.query.navigationItemSpacing) || undefined);
|
|
14
|
+
|
|
15
|
+
watchEffect(function applyFixtureTheme() {
|
|
16
|
+
const themeName = `${surface.value}-${themeMode.value}`;
|
|
17
|
+
theme.change(themeName);
|
|
18
|
+
document.body.dataset.surface = surface.value;
|
|
19
|
+
document.body.dataset.theme = themeMode.value;
|
|
20
|
+
});
|
|
21
|
+
</script>
|
|
22
|
+
|
|
23
|
+
<template>
|
|
24
|
+
<v-app>
|
|
25
|
+
<ShellLayout
|
|
26
|
+
:surface="surface"
|
|
27
|
+
:surface-label="surfaceLabel"
|
|
28
|
+
:rail-width="railWidth"
|
|
29
|
+
:navigation-item-spacing="navigationItemSpacing"
|
|
30
|
+
>
|
|
31
|
+
<RouterView />
|
|
32
|
+
</ShellLayout>
|
|
33
|
+
</v-app>
|
|
34
|
+
</template>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { computed } from "vue";
|
|
3
|
+
import { useRoute } from "vue-router";
|
|
4
|
+
|
|
5
|
+
const route = useRoute();
|
|
6
|
+
const title = computed(() => String(route.meta.title || "Shell fixture"));
|
|
7
|
+
</script>
|
|
8
|
+
|
|
9
|
+
<template>
|
|
10
|
+
<main class="generated-ui-screen" tabindex="-1">
|
|
11
|
+
<h1>{{ title }}</h1>
|
|
12
|
+
<p>Package-owned browser fixture for the adaptive shell contract.</p>
|
|
13
|
+
</main>
|
|
14
|
+
</template>
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import { createApp } from "vue";
|
|
2
|
+
import { createPinia } from "pinia";
|
|
3
|
+
import { createRouter, createWebHistory } from "vue-router";
|
|
4
|
+
import { createVuetify } from "vuetify";
|
|
5
|
+
import * as vuetifyComponents from "vuetify/components";
|
|
6
|
+
import * as vuetifyDirectives from "vuetify/directives";
|
|
7
|
+
import { aliases, mdi } from "vuetify/iconsets/mdi-svg";
|
|
8
|
+
import "vuetify/styles";
|
|
9
|
+
import App from "./App.vue";
|
|
10
|
+
import ScreenPage from "./ScreenPage.vue";
|
|
11
|
+
import ShellMenuLinkItem from "@jskit-ai/shell-web/client/components/ShellMenuLinkItem";
|
|
12
|
+
import ShellSurfaceAwareMenuLinkItem from "@jskit-ai/shell-web/client/components/ShellSurfaceAwareMenuLinkItem";
|
|
13
|
+
import ShellTabLinkItem from "@jskit-ai/shell-web/client/components/ShellTabLinkItem";
|
|
14
|
+
import { useShellLayoutStore } from "../../../src/client/stores/useShellLayoutStore.js";
|
|
15
|
+
import { createWebPlacementRuntime } from "../../../src/client/placement/runtime.js";
|
|
16
|
+
|
|
17
|
+
const COMPONENTS = new Map([
|
|
18
|
+
["fixture.menu-link", ShellMenuLinkItem],
|
|
19
|
+
["fixture.surface-menu-link", ShellSurfaceAwareMenuLinkItem],
|
|
20
|
+
["fixture.tab-link", ShellTabLinkItem]
|
|
21
|
+
]);
|
|
22
|
+
|
|
23
|
+
const placementContainer = Object.freeze({
|
|
24
|
+
has(token) {
|
|
25
|
+
return COMPONENTS.has(token);
|
|
26
|
+
},
|
|
27
|
+
make(token) {
|
|
28
|
+
return COMPONENTS.get(token);
|
|
29
|
+
},
|
|
30
|
+
resolveTag() {
|
|
31
|
+
return [];
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
function createSurfacePlacement(surface, order, label, suffix) {
|
|
36
|
+
return {
|
|
37
|
+
id: `fixture.${surface}.${label.toLowerCase()}`,
|
|
38
|
+
target: "shell.primary-nav",
|
|
39
|
+
kind: "link",
|
|
40
|
+
surfaces: [surface],
|
|
41
|
+
order,
|
|
42
|
+
props: {
|
|
43
|
+
label,
|
|
44
|
+
surface,
|
|
45
|
+
scopedSuffix: suffix,
|
|
46
|
+
unscopedSuffix: suffix,
|
|
47
|
+
exact: suffix === "/"
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function createSurfacePlacements(surface) {
|
|
53
|
+
return [
|
|
54
|
+
createSurfacePlacement(surface, 10, "Home", "/"),
|
|
55
|
+
createSurfacePlacement(surface, 20, "Assistant", "/assistant"),
|
|
56
|
+
createSurfacePlacement(surface, 30, "Contacts", "/contacts"),
|
|
57
|
+
createSurfacePlacement(surface, 40, "Bookings", "/bookings"),
|
|
58
|
+
createSurfacePlacement(surface, 50, "Rostering", "/rostering")
|
|
59
|
+
];
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const placements = [
|
|
63
|
+
...createSurfacePlacements("home"),
|
|
64
|
+
...createSurfacePlacements("admin"),
|
|
65
|
+
{
|
|
66
|
+
id: "fixture.help",
|
|
67
|
+
target: "shell.secondary-nav",
|
|
68
|
+
kind: "link",
|
|
69
|
+
componentToken: "fixture.menu-link",
|
|
70
|
+
surfaces: ["*"],
|
|
71
|
+
order: 100,
|
|
72
|
+
props: {
|
|
73
|
+
label: "Help and support",
|
|
74
|
+
to: "/help"
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
];
|
|
78
|
+
|
|
79
|
+
const menuRenderers = Object.freeze({ link: "fixture.surface-menu-link" });
|
|
80
|
+
const tabRenderers = Object.freeze({ link: "fixture.tab-link" });
|
|
81
|
+
const placementTopology = [
|
|
82
|
+
{
|
|
83
|
+
id: "shell.primary-nav",
|
|
84
|
+
surfaces: ["*"],
|
|
85
|
+
default: true,
|
|
86
|
+
variants: {
|
|
87
|
+
compact: { outlet: "shell-layout:primary-bottom-nav", renderers: tabRenderers },
|
|
88
|
+
medium: { outlet: "shell-layout:primary-menu", renderers: menuRenderers },
|
|
89
|
+
expanded: { outlet: "shell-layout:primary-menu", renderers: menuRenderers }
|
|
90
|
+
}
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
id: "shell.secondary-nav",
|
|
94
|
+
surfaces: ["*"],
|
|
95
|
+
variants: {
|
|
96
|
+
compact: { outlet: "shell-layout:secondary-menu", renderers: menuRenderers },
|
|
97
|
+
medium: { outlet: "shell-layout:secondary-menu", renderers: menuRenderers },
|
|
98
|
+
expanded: { outlet: "shell-layout:secondary-menu", renderers: menuRenderers }
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
];
|
|
102
|
+
|
|
103
|
+
const placementRuntime = createWebPlacementRuntime({ app: placementContainer });
|
|
104
|
+
placementRuntime.replacePlacements(placements);
|
|
105
|
+
placementRuntime.replacePlacementTopology(placementTopology);
|
|
106
|
+
placementRuntime.setContext({
|
|
107
|
+
surfaceConfig: {
|
|
108
|
+
tenancyMode: "path",
|
|
109
|
+
defaultSurfaceId: "home",
|
|
110
|
+
enabledSurfaceIds: ["home", "admin"],
|
|
111
|
+
surfacesById: {
|
|
112
|
+
home: { id: "home", routeBase: "/home", enabled: true },
|
|
113
|
+
admin: { id: "admin", routeBase: "/w/:workspaceSlug/admin", enabled: true }
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
const routes = [
|
|
119
|
+
{ path: "/", redirect: "/home" },
|
|
120
|
+
{ path: "/home/:section?", component: ScreenPage, meta: { title: "Home surface" } },
|
|
121
|
+
{ path: "/w/:workspaceSlug/admin/:section?", component: ScreenPage, meta: { title: "Admin surface" } },
|
|
122
|
+
{ path: "/help", component: ScreenPage, meta: { title: "Help and support" } }
|
|
123
|
+
];
|
|
124
|
+
const router = createRouter({ history: createWebHistory(), routes });
|
|
125
|
+
const pinia = createPinia();
|
|
126
|
+
const vuetify = createVuetify({
|
|
127
|
+
components: vuetifyComponents,
|
|
128
|
+
directives: vuetifyDirectives,
|
|
129
|
+
icons: {
|
|
130
|
+
defaultSet: "mdi",
|
|
131
|
+
aliases,
|
|
132
|
+
sets: { mdi }
|
|
133
|
+
},
|
|
134
|
+
theme: {
|
|
135
|
+
defaultTheme: "home-light",
|
|
136
|
+
themes: {
|
|
137
|
+
"home-light": {
|
|
138
|
+
dark: false,
|
|
139
|
+
colors: {
|
|
140
|
+
background: "#F4F7F5",
|
|
141
|
+
surface: "#FFFFFF",
|
|
142
|
+
"on-surface": "#34413F",
|
|
143
|
+
"surface-variant": "#E2E8E5",
|
|
144
|
+
"on-surface-variant": "#52605F",
|
|
145
|
+
primary: "#006C51"
|
|
146
|
+
}
|
|
147
|
+
},
|
|
148
|
+
"admin-light": {
|
|
149
|
+
dark: false,
|
|
150
|
+
colors: {
|
|
151
|
+
background: "#F5F6F4",
|
|
152
|
+
surface: "#FFFFFF",
|
|
153
|
+
"on-surface": "#34413F",
|
|
154
|
+
"surface-variant": "#424242",
|
|
155
|
+
"on-surface-variant": "#52605F",
|
|
156
|
+
primary: "#006C51"
|
|
157
|
+
}
|
|
158
|
+
},
|
|
159
|
+
"home-dark": {
|
|
160
|
+
dark: true,
|
|
161
|
+
colors: {
|
|
162
|
+
background: "#101513",
|
|
163
|
+
surface: "#18201D",
|
|
164
|
+
"on-surface": "#E1E9E5",
|
|
165
|
+
"surface-variant": "#2B3632",
|
|
166
|
+
"on-surface-variant": "#BEC9C4",
|
|
167
|
+
primary: "#67DBB7"
|
|
168
|
+
}
|
|
169
|
+
},
|
|
170
|
+
"admin-dark": {
|
|
171
|
+
dark: true,
|
|
172
|
+
colors: {
|
|
173
|
+
background: "#111412",
|
|
174
|
+
surface: "#1A201D",
|
|
175
|
+
"on-surface": "#E2E8E4",
|
|
176
|
+
"surface-variant": "#333936",
|
|
177
|
+
"on-surface-variant": "#606864",
|
|
178
|
+
primary: "#67DBB7"
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
const app = createApp(App);
|
|
186
|
+
app.use(pinia);
|
|
187
|
+
app.use(router);
|
|
188
|
+
app.use(vuetify);
|
|
189
|
+
app.provide("jskit.shell-web.runtime.web-placement.client", placementRuntime);
|
|
190
|
+
app.provide("jskit.shell-web.runtime.web-refresh.client", {
|
|
191
|
+
async refresh() {
|
|
192
|
+
await fetch("/api/bootstrap?reason=pull-to-refresh");
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
useShellLayoutStore(pinia).setDrawerDefaultOpen(true);
|
|
196
|
+
await router.isReady();
|
|
197
|
+
app.mount("#app");
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { fileURLToPath, URL } from "node:url";
|
|
2
|
+
import { defineConfig } from "vite";
|
|
3
|
+
import vue from "@vitejs/plugin-vue";
|
|
4
|
+
|
|
5
|
+
export default defineConfig({
|
|
6
|
+
root: fileURLToPath(new URL(".", import.meta.url)),
|
|
7
|
+
resolve: {
|
|
8
|
+
preserveSymlinks: true,
|
|
9
|
+
alias: {
|
|
10
|
+
"@": fileURLToPath(new URL("./src", import.meta.url))
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
plugins: [vue()],
|
|
14
|
+
server: {
|
|
15
|
+
host: "127.0.0.1"
|
|
16
|
+
}
|
|
17
|
+
});
|
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.151",
|
|
5
5
|
kind: "runtime",
|
|
6
6
|
description: "Web shell layout runtime with outlet-based placement contributions.",
|
|
7
7
|
dependsOn: [],
|
|
@@ -311,7 +311,7 @@ export default Object.freeze({
|
|
|
311
311
|
dependencies: {
|
|
312
312
|
runtime: {
|
|
313
313
|
"@mdi/js": "^7.4.47",
|
|
314
|
-
"@jskit-ai/kernel": "0.1.
|
|
314
|
+
"@jskit-ai/kernel": "0.1.148"
|
|
315
315
|
},
|
|
316
316
|
dev: {
|
|
317
317
|
"@playwright/test": "1.61.1"
|
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.151",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"test": "node --test"
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
30
|
"@mdi/js": "^7.4.47",
|
|
31
|
-
"@jskit-ai/kernel": "0.1.
|
|
31
|
+
"@jskit-ai/kernel": "0.1.148"
|
|
32
32
|
},
|
|
33
33
|
"peerDependencies": {
|
|
34
34
|
"pinia": "^3.0.4",
|