@jskit-ai/shell-web 0.1.151 → 0.1.152
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 +6 -0
- package/package.descriptor.mjs +1 -1
- package/package.json +1 -1
- package/src/client/components/ShellLayout.vue +22 -5
- package/src/test/adaptiveShellSmoke.js +30 -9
- package/test/adaptiveShell.browser.test.js +5 -0
- package/test/playwrightContract.test.js +1 -1
- package/test/settingsPlacementContract.test.js +4 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.152 - 2026-08-13
|
|
4
|
+
|
|
5
|
+
- Remove the redundant uppercase surface subheader from the expanded drawer; the top app bar remains the single shell-owned surface label.
|
|
6
|
+
- Align expanded drawer icons to the collapsed rail centreline so toggling navigation reveals or hides labels without shifting the icons horizontally.
|
|
7
|
+
- Preserve the Material 3 80px default rail and the public `railWidth` and `navigationItemSpacing` density controls.
|
|
8
|
+
|
|
3
9
|
## 0.1.151 - 2026-08-13
|
|
4
10
|
|
|
5
11
|
- 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.
|
package/package.descriptor.mjs
CHANGED
package/package.json
CHANGED
|
@@ -628,7 +628,10 @@ function touchListIncludesActiveTouch(touchList) {
|
|
|
628
628
|
:data-drawer-width="resolvedDrawerWidth"
|
|
629
629
|
:data-navigation-item-spacing="resolvedNavigationItemSpacing"
|
|
630
630
|
:data-rail-width="resolvedRailWidth"
|
|
631
|
-
:style="{
|
|
631
|
+
:style="{
|
|
632
|
+
'--shell-navigation-item-spacing': `${resolvedNavigationItemSpacing}px`,
|
|
633
|
+
'--shell-navigation-rail-width': `${resolvedRailWidth}px`
|
|
634
|
+
}"
|
|
632
635
|
:temporary="isCompactLayout"
|
|
633
636
|
:permanent="!isCompactLayout"
|
|
634
637
|
:width="resolvedDrawerWidth"
|
|
@@ -643,9 +646,6 @@ function touchListIncludesActiveTouch(touchList) {
|
|
|
643
646
|
class="pt-2"
|
|
644
647
|
:prepend-gap="resolvedNavigationItemSpacing"
|
|
645
648
|
>
|
|
646
|
-
<v-list-subheader v-if="!drawerPresentation.rail" class="text-uppercase text-caption">
|
|
647
|
-
{{ resolvedSurfaceLabel }}
|
|
648
|
-
</v-list-subheader>
|
|
649
649
|
<ShellOutlet
|
|
650
650
|
target="shell-layout:primary-menu"
|
|
651
651
|
default
|
|
@@ -734,9 +734,26 @@ function touchListIncludesActiveTouch(touchList) {
|
|
|
734
734
|
white-space: nowrap;
|
|
735
735
|
}
|
|
736
736
|
|
|
737
|
-
.shell-layout__drawer
|
|
737
|
+
.shell-layout__drawer {
|
|
738
|
+
--shell-navigation-drawer-inset: 12px;
|
|
739
|
+
--shell-navigation-icon-size: 24px;
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
.shell-layout__drawer[data-presentation="drawer"] :deep(.v-list--nav) {
|
|
743
|
+
padding-inline-end: calc(var(--shell-navigation-item-spacing) / 2);
|
|
744
|
+
padding-inline-start: var(--shell-navigation-drawer-inset);
|
|
745
|
+
}
|
|
746
|
+
|
|
738
747
|
.shell-layout__drawer[data-presentation="drawer"] :deep(.v-list-item) {
|
|
739
748
|
padding-inline-end: calc(var(--shell-navigation-item-spacing) / 2);
|
|
749
|
+
padding-inline-start: max(
|
|
750
|
+
0px,
|
|
751
|
+
calc(
|
|
752
|
+
var(--shell-navigation-rail-width) / 2 -
|
|
753
|
+
var(--shell-navigation-icon-size) / 2 -
|
|
754
|
+
var(--shell-navigation-drawer-inset)
|
|
755
|
+
)
|
|
756
|
+
);
|
|
740
757
|
}
|
|
741
758
|
|
|
742
759
|
.shell-layout__drawer[data-presentation="rail"] :deep(.v-list-item) {
|
|
@@ -177,22 +177,42 @@ async function expectContentAwareDrawerFit(drawer, expect) {
|
|
|
177
177
|
}
|
|
178
178
|
}
|
|
179
179
|
|
|
180
|
-
async function
|
|
180
|
+
async function readNavigationIconCenters(drawer) {
|
|
181
|
+
const icons = drawer.locator("a.shell-menu-link-item[href] .v-icon");
|
|
182
|
+
await icons.first().waitFor({ state: "visible" });
|
|
183
|
+
const centers = [];
|
|
184
|
+
const iconCount = await icons.count();
|
|
185
|
+
for (let index = 0; index < iconCount; index += 1) {
|
|
186
|
+
const box = await icons.nth(index).boundingBox();
|
|
187
|
+
if (!box) {
|
|
188
|
+
throw new Error("Shell navigation exposed an unmeasurable icon.");
|
|
189
|
+
}
|
|
190
|
+
centers.push(box.x + box.width / 2);
|
|
191
|
+
}
|
|
192
|
+
return centers;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
async function expectCentredRailNavigation(page, drawer, expect, expandedIconCenters) {
|
|
181
196
|
const links = drawer.locator("a.shell-menu-link-item[href]");
|
|
182
197
|
await expect(links.first()).toBeVisible();
|
|
183
198
|
const firstLink = links.first();
|
|
184
|
-
const
|
|
185
|
-
const
|
|
186
|
-
const [drawerBox, linkBox, iconBox] = geometry;
|
|
199
|
+
const geometry = await Promise.all([drawer.boundingBox(), firstLink.boundingBox()]);
|
|
200
|
+
const [drawerBox, linkBox] = geometry;
|
|
187
201
|
|
|
188
202
|
expect(drawerBox).not.toBeNull();
|
|
189
203
|
expect(linkBox).not.toBeNull();
|
|
190
|
-
expect(iconBox).not.toBeNull();
|
|
191
204
|
expect(linkBox.width).toBeGreaterThanOrEqual(48);
|
|
192
205
|
expect(linkBox.height).toBeGreaterThanOrEqual(48);
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
206
|
+
const railIconCenters = await readNavigationIconCenters(drawer);
|
|
207
|
+
expect(railIconCenters.length).toBe(expandedIconCenters.length);
|
|
208
|
+
for (let index = 0; index < railIconCenters.length; index += 1) {
|
|
209
|
+
expect(Math.abs(
|
|
210
|
+
railIconCenters[index] - (drawerBox.x + drawerBox.width / 2)
|
|
211
|
+
)).toBeLessThanOrEqual(1);
|
|
212
|
+
expect(Math.abs(
|
|
213
|
+
railIconCenters[index] - expandedIconCenters[index]
|
|
214
|
+
)).toBeLessThanOrEqual(1);
|
|
215
|
+
}
|
|
196
216
|
|
|
197
217
|
const currentUrl = new URL(page.url());
|
|
198
218
|
const linkCount = await links.count();
|
|
@@ -288,13 +308,14 @@ async function runAdaptiveShellSmokeCase({
|
|
|
288
308
|
await expect(drawer).toHaveAttribute("data-presentation", "drawer");
|
|
289
309
|
await expectDrawerWidth(drawer, expect, await readConfiguredWidth(drawer, "data-drawer-width"));
|
|
290
310
|
await expectContentAwareDrawerFit(drawer, expect);
|
|
311
|
+
const expandedIconCenters = await readNavigationIconCenters(drawer);
|
|
291
312
|
await page.keyboard.press("Escape");
|
|
292
313
|
await expect(drawer).toHaveAttribute("data-presentation", "drawer");
|
|
293
314
|
|
|
294
315
|
await toggle.click();
|
|
295
316
|
await expect(drawer).toHaveAttribute("data-presentation", "rail");
|
|
296
317
|
await expectDrawerWidth(drawer, expect, await readConfiguredWidth(drawer, "data-rail-width"));
|
|
297
|
-
await expectCentredRailNavigation(page, drawer, expect);
|
|
318
|
+
await expectCentredRailNavigation(page, drawer, expect, expandedIconCenters);
|
|
298
319
|
await expectNoHorizontalOverflow(page, expect);
|
|
299
320
|
}
|
|
300
321
|
}
|
|
@@ -166,6 +166,11 @@ async function assertTooltipContrast(page, linkName, interaction) {
|
|
|
166
166
|
async function assertDrawerFit(page) {
|
|
167
167
|
const drawer = page.getByTestId("jskit-shell-drawer");
|
|
168
168
|
await expect(drawer).toHaveAttribute("data-presentation", "drawer");
|
|
169
|
+
assert.equal(
|
|
170
|
+
await drawer.locator(".v-list-subheader").count(),
|
|
171
|
+
0,
|
|
172
|
+
"The drawer must not repeat the surface label from the top app bar."
|
|
173
|
+
);
|
|
169
174
|
const configuredSpacing = Number(await drawer.getAttribute("data-navigation-item-spacing"));
|
|
170
175
|
assert.ok(Number.isFinite(configuredSpacing));
|
|
171
176
|
await expect.poll(async () => {
|
|
@@ -35,7 +35,7 @@ test("adaptive shell smoke follows rendered layout state and waits for drawer tr
|
|
|
35
35
|
assert.match(source, /toBeFocused/u);
|
|
36
36
|
assert.match(source, /v-navigation-drawer__scrim/u);
|
|
37
37
|
assert.match(source, /page\.keyboard\.press\("Escape"\)[\s\S]*data-presentation", "drawer"/u);
|
|
38
|
-
assert.match(source, /
|
|
38
|
+
assert.match(source, /centers\.push\(box\.x \+ box\.width \/ 2\)/u);
|
|
39
39
|
assert.match(source, /fit\.endGap - configuredSpacing/u);
|
|
40
40
|
assert.match(source, /fit\.iconLabelGaps/u);
|
|
41
41
|
assert.doesNotMatch(source, /viewport\.name === "compact"/u);
|
|
@@ -110,6 +110,10 @@ test("shell-web shell layout registers navigation at the app layout level", asyn
|
|
|
110
110
|
assert.match(source, /measureDrawerContentWidth/);
|
|
111
111
|
assert.match(source, /DEFAULT_SHELL_NAVIGATION_ITEM_SPACING/);
|
|
112
112
|
assert.match(source, /:prepend-gap="resolvedNavigationItemSpacing"/);
|
|
113
|
+
assert.match(source, /--shell-navigation-drawer-inset:\s*12px/);
|
|
114
|
+
assert.match(source, /--shell-navigation-rail-width/);
|
|
115
|
+
assert.match(source, /padding-inline-start:\s*var\(--shell-navigation-drawer-inset\)/);
|
|
116
|
+
assert.doesNotMatch(source, /<v-list-subheader/);
|
|
113
117
|
assert.doesNotMatch(source, /:width="248"/);
|
|
114
118
|
assert.doesNotMatch(source, /:rail-width="80"/);
|
|
115
119
|
assert.match(source, /:model-value="drawerPresentation\.visible"/);
|