@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 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.
@@ -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.151",
4
+ version: "0.1.152",
5
5
  kind: "runtime",
6
6
  description: "Web shell layout runtime with outlet-based placement contributions.",
7
7
  dependsOn: [],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jskit-ai/shell-web",
3
- "version": "0.1.151",
3
+ "version": "0.1.152",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "test": "node --test"
@@ -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="{ '--shell-navigation-item-spacing': `${resolvedNavigationItemSpacing}px` }"
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[data-presentation="drawer"] :deep(.v-list--nav),
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 expectCentredRailNavigation(page, drawer, expect) {
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 firstIcon = firstLink.locator(".v-icon").first();
185
- const geometry = await Promise.all([drawer.boundingBox(), firstLink.boundingBox(), firstIcon.boundingBox()]);
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
- expect(Math.abs(
194
- iconBox.x + iconBox.width / 2 - (drawerBox.x + drawerBox.width / 2)
195
- )).toBeLessThanOrEqual(1);
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, /iconBox\.x \+ iconBox\.width \/ 2/u);
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"/);