@jskit-ai/shell-web 0.1.179 → 0.1.181

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.
@@ -5,7 +5,6 @@ import vue from "@vitejs/plugin-vue";
5
5
  export default defineConfig({
6
6
  root: fileURLToPath(new URL(".", import.meta.url)),
7
7
  resolve: {
8
- preserveSymlinks: true,
9
8
  alias: {
10
9
  "@": fileURLToPath(new URL("./src", import.meta.url))
11
10
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jskit-ai/shell-web",
3
- "version": "0.1.179",
3
+ "version": "0.1.181",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "test": "node --test"
@@ -31,7 +31,7 @@
31
31
  "@mdi/js": "^7.4.47"
32
32
  },
33
33
  "peerDependencies": {
34
- "@jskit-ai/kernel": "0.1.175",
34
+ "@jskit-ai/kernel": "0.1.177",
35
35
  "pinia": "^3.0.4",
36
36
  "vue": "^3.5.13",
37
37
  "vue-router": "^5.0.4",
@@ -17,6 +17,7 @@ const theme = useTheme();
17
17
  :theme="theme.name.value"
18
18
  location="end"
19
19
  content-class="shell-navigation-tooltip"
20
+ :open-delay="400"
20
21
  :open-on-focus="true"
21
22
  :open-on-hover="true"
22
23
  >
@@ -32,6 +33,5 @@ const theme = useTheme();
32
33
  rgb(var(--v-theme-inverse-surface, var(--v-theme-on-surface))) !important;
33
34
  color:
34
35
  rgb(var(--v-theme-inverse-on-surface, var(--v-theme-surface))) !important;
35
- opacity: 1 !important;
36
36
  }
37
37
  </style>
@@ -57,6 +57,7 @@ async function assertTooltipContrast(page, linkName, interaction) {
57
57
  }
58
58
  const tooltip = page.locator(".shell-navigation-tooltip").filter({ hasText: linkName }).last();
59
59
  await expect(tooltip).toBeVisible();
60
+ await expect(tooltip).toHaveCSS("opacity", "1");
60
61
  const colors = await tooltip.evaluate((element) => {
61
62
  const style = window.getComputedStyle(element);
62
63
  return {
@@ -76,6 +77,49 @@ async function assertTooltipContrast(page, linkName, interaction) {
76
77
  await page.keyboard.press("Escape");
77
78
  }
78
79
 
80
+ async function assertRapidTooltipTraversal(page) {
81
+ const result = await page.locator(".shell-menu-link-item").evaluateAll(async (links) => {
82
+ const pause = (duration) => new Promise((resolve) => window.setTimeout(resolve, duration));
83
+ const countVisibleTooltips = () => Array.from(
84
+ document.querySelectorAll(".shell-navigation-tooltip")
85
+ ).filter((element) => {
86
+ const style = window.getComputedStyle(element);
87
+ const rect = element.getBoundingClientRect();
88
+ return (
89
+ style.display !== "none" &&
90
+ style.visibility !== "hidden" &&
91
+ Number(style.opacity) > 0 &&
92
+ rect.width > 0 &&
93
+ rect.height > 0
94
+ );
95
+ }).length;
96
+
97
+ const samples = [];
98
+ let activeLink = null;
99
+ for (const link of links) {
100
+ activeLink?.dispatchEvent(new MouseEvent("mouseleave"));
101
+ link.dispatchEvent(new MouseEvent("mouseenter"));
102
+ activeLink = link;
103
+ await pause(24);
104
+ samples.push(countVisibleTooltips());
105
+ }
106
+ activeLink?.dispatchEvent(new MouseEvent("mouseleave"));
107
+ await pause(100);
108
+
109
+ return {
110
+ afterLeaving: countVisibleTooltips(),
111
+ maximumVisible: Math.max(0, ...samples)
112
+ };
113
+ });
114
+
115
+ assert.equal(
116
+ result.maximumVisible,
117
+ 0,
118
+ "Briefly crossing navigation links must not leave tooltip overlays on screen."
119
+ );
120
+ assert.equal(result.afterLeaving, 0, "Navigation tooltips must remain hidden after traversal.");
121
+ }
122
+
79
123
  async function assertDrawerFit(page) {
80
124
  const drawer = page.getByTestId("jskit-shell-drawer");
81
125
  await expect(drawer).toHaveAttribute("data-presentation", "drawer");
@@ -203,11 +247,22 @@ test("shell-web adaptive navigation passes package-owned browser contracts", {
203
247
  await page.goto(pathAndTheme);
204
248
  await expect(page.getByTestId("jskit-shell-drawer")).toBeVisible();
205
249
  await assertDrawerFit(page);
250
+ await assertRapidTooltipTraversal(page);
206
251
  await assertTooltipContrast(page, "Bookings", "focus");
207
252
  await assertTooltipContrast(page, "Help and support", "hover");
208
253
  await page.close();
209
254
  }
210
255
 
256
+ const mediumTooltipPage = await context.newPage();
257
+ await mediumTooltipPage.setViewportSize({ width: 1024, height: 1024 });
258
+ await mediumTooltipPage.goto("/w/acme/admin/bookings?theme=light");
259
+ await expect(mediumTooltipPage.getByTestId("jskit-shell-drawer")).toHaveAttribute(
260
+ "data-layout",
261
+ "medium"
262
+ );
263
+ await assertRapidTooltipTraversal(mediumTooltipPage);
264
+ await mediumTooltipPage.close();
265
+
211
266
  const railPage = await context.newPage();
212
267
  await railPage.setViewportSize({ width: 1280, height: 900 });
213
268
  await railPage.goto("/w/acme/admin/bookings?theme=light");
@@ -126,6 +126,16 @@ test("shell-web shell layout registers navigation at the app layout level", asyn
126
126
  assert.doesNotMatch(template, /ShellOutlet|ShellRouteTransition|useShellLayoutState|pointerdown|v-navigation-drawer|v-bottom-navigation/);
127
127
  });
128
128
 
129
+ test("shell navigation tooltips ignore incidental pointer traversal", async () => {
130
+ const source = await readFile(
131
+ path.join(PACKAGE_DIR, "src", "client", "components", "ShellNavigationTooltip.vue"),
132
+ "utf8"
133
+ );
134
+
135
+ assert.match(source, /:open-delay="400"/u);
136
+ assert.doesNotMatch(source, /opacity:\s*1\s*!important/u);
137
+ });
138
+
129
139
  test("shell-web error host uses one explicit close affordance for banner errors", async () => {
130
140
  const source = await readFile(path.join(PACKAGE_DIR, "src", "client", "components", "ShellErrorHost.vue"), "utf8");
131
141