@jskit-ai/shell-web 0.1.187 → 0.1.189
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,14 +1,25 @@
|
|
|
1
1
|
<script setup>
|
|
2
|
-
import { computed } from "vue";
|
|
2
|
+
import { computed, ref } from "vue";
|
|
3
3
|
import { useRoute } from "vue-router";
|
|
4
4
|
|
|
5
5
|
const route = useRoute();
|
|
6
6
|
const title = computed(() => String(route.meta.title || "Shell fixture"));
|
|
7
|
+
const answer = ref("");
|
|
8
|
+
const validation = ref("");
|
|
7
9
|
</script>
|
|
8
10
|
|
|
9
11
|
<template>
|
|
10
12
|
<main tabindex="-1">
|
|
11
13
|
<h1>{{ title }}</h1>
|
|
12
14
|
<p>Package-owned browser fixture for the adaptive shell contract.</p>
|
|
15
|
+
<form @submit.prevent="validation = 'Please review this answer.'">
|
|
16
|
+
<label>Unsaved answer <input v-model="answer" name="answer"></label>
|
|
17
|
+
<details>
|
|
18
|
+
<summary>More details</summary>
|
|
19
|
+
<p>Expanded form section.</p>
|
|
20
|
+
</details>
|
|
21
|
+
<button type="submit">Validate answer</button>
|
|
22
|
+
<p role="status">{{ validation }}</p>
|
|
23
|
+
</form>
|
|
13
24
|
</main>
|
|
14
25
|
</template>
|
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.189",
|
|
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.
|
|
34
|
+
"@jskit-ai/kernel": "0.1.185",
|
|
35
35
|
"pinia": "^3.0.4",
|
|
36
36
|
"vue": "^3.5.13",
|
|
37
37
|
"vue-router": "^5.0.4",
|
|
@@ -233,16 +233,20 @@ const routeTransitionName = computed(() => {
|
|
|
233
233
|
return "";
|
|
234
234
|
});
|
|
235
235
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
236
|
+
// Animation can change with viewport geometry; pane identity changes only with
|
|
237
|
+
// navigation. Resizing must not recreate the routed form or lose focus.
|
|
238
|
+
const routeTransitionKey = ref("");
|
|
239
|
+
watch(
|
|
240
|
+
[() => normalizeComparablePathname(route?.path || route?.fullPath || "/"), currentSurfaceId],
|
|
241
|
+
([routePathKey, surfaceId]) => {
|
|
242
|
+
routeTransitionKey.value = resolveShellRouteTransitionKey({
|
|
243
|
+
routePathKey,
|
|
244
|
+
routeTransitionName: routeTransitionName.value,
|
|
245
|
+
surfaceId
|
|
246
|
+
});
|
|
247
|
+
},
|
|
248
|
+
{ immediate: true, flush: "sync" }
|
|
249
|
+
);
|
|
246
250
|
|
|
247
251
|
const swipeNavigationEnabled = computed(() =>
|
|
248
252
|
Boolean(
|
|
@@ -301,3 +301,63 @@ test("shell-web adaptive navigation passes package-owned browser contracts", {
|
|
|
301
301
|
await stopProcess(vite);
|
|
302
302
|
}
|
|
303
303
|
});
|
|
304
|
+
|
|
305
|
+
test("shell-web preserves the active form across responsive changes", {
|
|
306
|
+
skip: RUN_BROWSER_TEST ? false : "set JSKIT_SHELL_WEB_BROWSER_INTEGRATION=1",
|
|
307
|
+
timeout: 90_000
|
|
308
|
+
}, async () => {
|
|
309
|
+
const vite = await startViteFixture({ fixtureRoot: FIXTURE_ROOT });
|
|
310
|
+
let browser;
|
|
311
|
+
try {
|
|
312
|
+
browser = await chromium.launch(createChromiumLaunchOptions());
|
|
313
|
+
for (const startingWidth of [1280, 390]) {
|
|
314
|
+
const page = await browser.newPage({ baseURL: vite.baseURL, viewport: { width: startingWidth, height: 900 } });
|
|
315
|
+
await page.goto("/w/acme/admin/contacts");
|
|
316
|
+
await page.getByRole("link", { name: "Bookings", exact: true }).click();
|
|
317
|
+
await expect(page).toHaveURL(/\/bookings$/u);
|
|
318
|
+
await expect(page.locator(".shell-route-transition__pane")).toHaveCount(1);
|
|
319
|
+
const answer = page.getByRole("textbox", { name: "Unsaved answer" });
|
|
320
|
+
await answer.fill("Keep this unsaved answer");
|
|
321
|
+
await page.getByText("More details", { exact: true }).click();
|
|
322
|
+
await page.getByRole("button", { name: "Validate answer" }).click();
|
|
323
|
+
await answer.focus();
|
|
324
|
+
const originalInput = await answer.elementHandle();
|
|
325
|
+
for (const viewport of [
|
|
326
|
+
{ width: 390, height: 844 },
|
|
327
|
+
{ width: 1024, height: 1024 },
|
|
328
|
+
{ width: 1280, height: 900 },
|
|
329
|
+
{ width: 1280, height: 420 },
|
|
330
|
+
{ width: 390, height: 420 },
|
|
331
|
+
{ width: 390, height: 844 }
|
|
332
|
+
]) {
|
|
333
|
+
await page.setViewportSize(viewport);
|
|
334
|
+
await page.evaluate(() => new Promise((resolve) => requestAnimationFrame(() => requestAnimationFrame(resolve))));
|
|
335
|
+
await expect(answer).toHaveValue("Keep this unsaved answer");
|
|
336
|
+
await expect(page.locator("details")).toHaveAttribute("open", "");
|
|
337
|
+
await expect(page.getByRole("status")).toHaveText("Please review this answer.");
|
|
338
|
+
await expect(answer).toBeFocused();
|
|
339
|
+
assert.equal(await originalInput.evaluate((element) => element === document.activeElement), true);
|
|
340
|
+
await expect(page.locator(".shell-route-transition__pane")).toHaveCount(1);
|
|
341
|
+
await expect(page.getByRole("heading", { level: 1 })).toHaveCount(1);
|
|
342
|
+
}
|
|
343
|
+
// Real compact navigation still replaces the pane and runs its animation.
|
|
344
|
+
await page.getByRole("link", { name: "Contacts", exact: true }).click();
|
|
345
|
+
await expect(page).toHaveURL(/\/contacts$/u);
|
|
346
|
+
await expect(page.locator(".shell-route-transition__pane")).toHaveCount(1);
|
|
347
|
+
await expect(answer).toHaveValue("");
|
|
348
|
+
assert.equal(await originalInput.evaluate((element) => element.isConnected), false);
|
|
349
|
+
// Desktop same-surface navigation continues to reuse the routed component.
|
|
350
|
+
await page.setViewportSize({ width: 1280, height: 900 });
|
|
351
|
+
await page.getByRole("link", { name: "Bookings", exact: true }).click();
|
|
352
|
+
await expect(page).toHaveURL(/\/bookings$/u);
|
|
353
|
+
await answer.fill("Desktop state");
|
|
354
|
+
await page.getByRole("link", { name: "Contacts", exact: true }).click();
|
|
355
|
+
await expect(page).toHaveURL(/\/contacts$/u);
|
|
356
|
+
await expect(answer).toHaveValue("Desktop state");
|
|
357
|
+
await page.close();
|
|
358
|
+
}
|
|
359
|
+
} finally {
|
|
360
|
+
await browser?.close();
|
|
361
|
+
await stopProcess(vite);
|
|
362
|
+
}
|
|
363
|
+
});
|
|
@@ -262,10 +262,9 @@ test("shell-web route transition keeps mobile route motion placement-driven", as
|
|
|
262
262
|
assert.match(source, /\.shell-route-transition__pane\s*\{[\s\S]*flex:\s*1 1 auto/);
|
|
263
263
|
assert.match(source, /\.shell-route-transition__pane\s*\{[\s\S]*min-height:\s*0/);
|
|
264
264
|
assert.match(source, /transitionDirection\.value = nextIndex > previousIndex \? "forward" : "reverse"/);
|
|
265
|
-
assert.match(
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
);
|
|
265
|
+
assert.match(source, /const routeTransitionKey = ref\(""\)/);
|
|
266
|
+
assert.match(source, /\[\(\) => normalizeComparablePathname\(route\?\.path \|\| route\?\.fullPath \|\| "\/"\), currentSurfaceId\]/);
|
|
267
|
+
assert.match(source, /routeTransitionKey\.value = resolveShellRouteTransitionKey/);
|
|
269
268
|
assert.match(source, /prefers-reduced-motion:\s*reduce/);
|
|
270
269
|
});
|
|
271
270
|
|