@jskit-ai/shell-web 0.1.150 → 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 +5 -0
- package/fixtures/adaptive-shell/src/App.vue +8 -1
- package/package.descriptor.mjs +1 -1
- package/package.json +1 -1
- package/src/client/components/ShellLayout.vue +20 -5
- package/src/client/support/drawerWidth.js +24 -7
- package/src/test/adaptiveShellSmoke.js +16 -3
- package/templates/src/components/ShellLayout.vue +1 -1
- package/test/adaptiveShell.browser.test.js +36 -2
- package/test/drawerWidth.test.js +14 -3
- package/test/playwrightContract.test.js +3 -1
- package/test/settingsPlacementContract.test.js +5 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
# Changelog
|
|
2
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
|
+
|
|
3
8
|
## 0.1.150 - 2026-08-13
|
|
4
9
|
|
|
5
10
|
- Give shell navigation tooltips one opaque, theme-owned Material color pair on hover and keyboard focus.
|
|
@@ -9,6 +9,8 @@ const theme = useTheme();
|
|
|
9
9
|
const surface = computed(() => route.path.startsWith("/w/") ? "admin" : "home");
|
|
10
10
|
const surfaceLabel = computed(() => surface.value === "admin" ? "Admin" : "Home");
|
|
11
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);
|
|
12
14
|
|
|
13
15
|
watchEffect(function applyFixtureTheme() {
|
|
14
16
|
const themeName = `${surface.value}-${themeMode.value}`;
|
|
@@ -20,7 +22,12 @@ watchEffect(function applyFixtureTheme() {
|
|
|
20
22
|
|
|
21
23
|
<template>
|
|
22
24
|
<v-app>
|
|
23
|
-
<ShellLayout
|
|
25
|
+
<ShellLayout
|
|
26
|
+
:surface="surface"
|
|
27
|
+
:surface-label="surfaceLabel"
|
|
28
|
+
:rail-width="railWidth"
|
|
29
|
+
:navigation-item-spacing="navigationItemSpacing"
|
|
30
|
+
>
|
|
24
31
|
<RouterView />
|
|
25
32
|
</ShellLayout>
|
|
26
33
|
</v-app>
|
package/package.descriptor.mjs
CHANGED
package/package.json
CHANGED
|
@@ -16,9 +16,10 @@ import {
|
|
|
16
16
|
} from "../support/drawerPresentation.js";
|
|
17
17
|
import {
|
|
18
18
|
DEFAULT_SHELL_DRAWER_WIDTH,
|
|
19
|
+
DEFAULT_SHELL_NAVIGATION_ITEM_SPACING,
|
|
19
20
|
DEFAULT_SHELL_RAIL_WIDTH,
|
|
20
|
-
SHELL_DRAWER_LABEL_END_GAP,
|
|
21
21
|
normalizeShellDrawerWidth,
|
|
22
|
+
normalizeShellNavigationItemSpacing,
|
|
22
23
|
normalizeShellRailWidth,
|
|
23
24
|
resolveContentAwareDrawerWidth
|
|
24
25
|
} from "../support/drawerWidth.js";
|
|
@@ -53,6 +54,10 @@ const props = defineProps({
|
|
|
53
54
|
railWidth: {
|
|
54
55
|
type: Number,
|
|
55
56
|
default: DEFAULT_SHELL_RAIL_WIDTH
|
|
57
|
+
},
|
|
58
|
+
navigationItemSpacing: {
|
|
59
|
+
type: Number,
|
|
60
|
+
default: DEFAULT_SHELL_NAVIGATION_ITEM_SPACING
|
|
56
61
|
}
|
|
57
62
|
});
|
|
58
63
|
|
|
@@ -111,6 +116,9 @@ const resolvedDrawerWidth = computed(() => {
|
|
|
111
116
|
return normalizeShellDrawerWidth(measuredDrawerWidth.value);
|
|
112
117
|
});
|
|
113
118
|
const resolvedRailWidth = computed(() => normalizeShellRailWidth(props.railWidth));
|
|
119
|
+
const resolvedNavigationItemSpacing = computed(() =>
|
|
120
|
+
normalizeShellNavigationItemSpacing(props.navigationItemSpacing)
|
|
121
|
+
);
|
|
114
122
|
const pullProgress = computed(() =>
|
|
115
123
|
Math.min(100, Math.round((pullDistance.value / PULL_REFRESH_TRIGGER_DISTANCE) * 100))
|
|
116
124
|
);
|
|
@@ -140,6 +148,7 @@ watch(
|
|
|
140
148
|
|
|
141
149
|
watch(drawerPresentation, scheduleDrawerWidthMeasurement, { flush: "post" });
|
|
142
150
|
watch(resolvedSurface, scheduleDrawerWidthMeasurement, { flush: "post" });
|
|
151
|
+
watch(resolvedNavigationItemSpacing, scheduleDrawerWidthMeasurement, { flush: "post" });
|
|
143
152
|
|
|
144
153
|
onMounted(attachShellListeners);
|
|
145
154
|
onBeforeUnmount(detachShellListeners);
|
|
@@ -299,7 +308,7 @@ function measureDrawerContentWidth() {
|
|
|
299
308
|
}
|
|
300
309
|
|
|
301
310
|
const nextWidth = resolveContentAwareDrawerWidth(measurements, {
|
|
302
|
-
endGap:
|
|
311
|
+
endGap: resolvedNavigationItemSpacing.value + endBorderWidth
|
|
303
312
|
});
|
|
304
313
|
if (Math.abs(nextWidth - measuredDrawerWidth.value) >= 0.25) {
|
|
305
314
|
measuredDrawerWidth.value = nextWidth;
|
|
@@ -617,8 +626,9 @@ function touchListIncludesActiveTouch(touchList) {
|
|
|
617
626
|
:data-presentation="drawerPresentation.kind"
|
|
618
627
|
:data-drawer-width-mode="props.drawerWidth === null || props.drawerWidth === undefined ? 'content' : 'fixed'"
|
|
619
628
|
:data-drawer-width="resolvedDrawerWidth"
|
|
629
|
+
:data-navigation-item-spacing="resolvedNavigationItemSpacing"
|
|
620
630
|
:data-rail-width="resolvedRailWidth"
|
|
621
|
-
:style="{ '--shell-
|
|
631
|
+
:style="{ '--shell-navigation-item-spacing': `${resolvedNavigationItemSpacing}px` }"
|
|
622
632
|
:temporary="isCompactLayout"
|
|
623
633
|
:permanent="!isCompactLayout"
|
|
624
634
|
:width="resolvedDrawerWidth"
|
|
@@ -627,7 +637,12 @@ function touchListIncludesActiveTouch(touchList) {
|
|
|
627
637
|
@update:model-value="handleDrawerVisibilityChange"
|
|
628
638
|
>
|
|
629
639
|
<slot name="menu" :surface="resolvedSurface" :rail="drawerPresentation.rail">
|
|
630
|
-
<v-list
|
|
640
|
+
<v-list
|
|
641
|
+
nav
|
|
642
|
+
density="comfortable"
|
|
643
|
+
class="pt-2"
|
|
644
|
+
:prepend-gap="resolvedNavigationItemSpacing"
|
|
645
|
+
>
|
|
631
646
|
<v-list-subheader v-if="!drawerPresentation.rail" class="text-uppercase text-caption">
|
|
632
647
|
{{ resolvedSurfaceLabel }}
|
|
633
648
|
</v-list-subheader>
|
|
@@ -721,7 +736,7 @@ function touchListIncludesActiveTouch(touchList) {
|
|
|
721
736
|
|
|
722
737
|
.shell-layout__drawer[data-presentation="drawer"] :deep(.v-list--nav),
|
|
723
738
|
.shell-layout__drawer[data-presentation="drawer"] :deep(.v-list-item) {
|
|
724
|
-
padding-inline-end: calc(var(--shell-
|
|
739
|
+
padding-inline-end: calc(var(--shell-navigation-item-spacing) / 2);
|
|
725
740
|
}
|
|
726
741
|
|
|
727
742
|
.shell-layout__drawer[data-presentation="rail"] :deep(.v-list-item) {
|
|
@@ -4,7 +4,9 @@ const MINIMUM_SHELL_DRAWER_WIDTH = 120;
|
|
|
4
4
|
const MAXIMUM_SHELL_DRAWER_WIDTH = 360;
|
|
5
5
|
const MINIMUM_SHELL_RAIL_WIDTH = 48;
|
|
6
6
|
const MAXIMUM_SHELL_RAIL_WIDTH = 160;
|
|
7
|
-
const
|
|
7
|
+
const DEFAULT_SHELL_NAVIGATION_ITEM_SPACING = 12;
|
|
8
|
+
const MINIMUM_SHELL_NAVIGATION_ITEM_SPACING = 8;
|
|
9
|
+
const MAXIMUM_SHELL_NAVIGATION_ITEM_SPACING = 24;
|
|
8
10
|
|
|
9
11
|
function clampNumber(value, minimum, maximum) {
|
|
10
12
|
return Math.min(maximum, Math.max(minimum, value));
|
|
@@ -34,10 +36,25 @@ function normalizeShellRailWidth(value, fallback = DEFAULT_SHELL_RAIL_WIDTH) {
|
|
|
34
36
|
);
|
|
35
37
|
}
|
|
36
38
|
|
|
39
|
+
function normalizeShellNavigationItemSpacing(
|
|
40
|
+
value,
|
|
41
|
+
fallback = DEFAULT_SHELL_NAVIGATION_ITEM_SPACING
|
|
42
|
+
) {
|
|
43
|
+
const parsed = Number(value);
|
|
44
|
+
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
45
|
+
return fallback;
|
|
46
|
+
}
|
|
47
|
+
return clampNumber(
|
|
48
|
+
Math.round(parsed),
|
|
49
|
+
MINIMUM_SHELL_NAVIGATION_ITEM_SPACING,
|
|
50
|
+
MAXIMUM_SHELL_NAVIGATION_ITEM_SPACING
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
37
54
|
function resolveContentAwareDrawerWidth(
|
|
38
55
|
measurements = [],
|
|
39
56
|
{
|
|
40
|
-
endGap =
|
|
57
|
+
endGap = DEFAULT_SHELL_NAVIGATION_ITEM_SPACING,
|
|
41
58
|
minimum = MINIMUM_SHELL_DRAWER_WIDTH,
|
|
42
59
|
maximum = MAXIMUM_SHELL_DRAWER_WIDTH,
|
|
43
60
|
fallback = DEFAULT_SHELL_DRAWER_WIDTH
|
|
@@ -56,19 +73,19 @@ function resolveContentAwareDrawerWidth(
|
|
|
56
73
|
if (requiredWidth <= 0) {
|
|
57
74
|
return normalizeShellDrawerWidth(fallback);
|
|
58
75
|
}
|
|
59
|
-
//
|
|
60
|
-
//
|
|
61
|
-
|
|
62
|
-
return clampNumber(Math.ceil(requiredWidth), minimum, maximum);
|
|
76
|
+
// Preserve subpixel font metrics while rounding outward enough to avoid
|
|
77
|
+
// clipping the final glyph at the exact spacing boundary.
|
|
78
|
+
return clampNumber(Math.ceil(requiredWidth * 4) / 4, minimum, maximum);
|
|
63
79
|
}
|
|
64
80
|
|
|
65
81
|
export {
|
|
66
82
|
DEFAULT_SHELL_DRAWER_WIDTH,
|
|
83
|
+
DEFAULT_SHELL_NAVIGATION_ITEM_SPACING,
|
|
67
84
|
DEFAULT_SHELL_RAIL_WIDTH,
|
|
68
85
|
MAXIMUM_SHELL_DRAWER_WIDTH,
|
|
69
86
|
MINIMUM_SHELL_DRAWER_WIDTH,
|
|
70
|
-
SHELL_DRAWER_LABEL_END_GAP,
|
|
71
87
|
normalizeShellDrawerWidth,
|
|
88
|
+
normalizeShellNavigationItemSpacing,
|
|
72
89
|
normalizeShellRailWidth,
|
|
73
90
|
resolveContentAwareDrawerWidth
|
|
74
91
|
};
|
|
@@ -104,6 +104,8 @@ async function expectContentAwareDrawerFit(drawer, expect) {
|
|
|
104
104
|
if (await drawer.getAttribute("data-drawer-width-mode") !== "content") {
|
|
105
105
|
return;
|
|
106
106
|
}
|
|
107
|
+
const configuredSpacing = Number(await drawer.getAttribute("data-navigation-item-spacing"));
|
|
108
|
+
expect(Number.isFinite(configuredSpacing)).toBe(true);
|
|
107
109
|
|
|
108
110
|
const fit = await drawer.evaluate((element) => {
|
|
109
111
|
const drawerRect = element.getBoundingClientRect();
|
|
@@ -125,6 +127,8 @@ async function expectContentAwareDrawerFit(drawer, expect) {
|
|
|
125
127
|
range.selectNodeContents(label);
|
|
126
128
|
const textRect = range.getBoundingClientRect();
|
|
127
129
|
range.detach?.();
|
|
130
|
+
const icon = label.closest(".v-list-item")?.querySelector(".v-icon");
|
|
131
|
+
const iconRect = icon?.getBoundingClientRect();
|
|
128
132
|
return {
|
|
129
133
|
clipped: (
|
|
130
134
|
label.scrollWidth > label.clientWidth ||
|
|
@@ -133,6 +137,9 @@ async function expectContentAwareDrawerFit(drawer, expect) {
|
|
|
133
137
|
? textRect.left < innerEnd - 1
|
|
134
138
|
: textRect.right > innerEnd + 1)
|
|
135
139
|
),
|
|
140
|
+
iconLabelGap: iconRect
|
|
141
|
+
? rightToLeft ? iconRect.left - textRect.right : textRect.left - iconRect.right
|
|
142
|
+
: null,
|
|
136
143
|
logicalEnd: rightToLeft ? textRect.left : textRect.right,
|
|
137
144
|
width: textRect.width
|
|
138
145
|
};
|
|
@@ -154,14 +161,20 @@ async function expectContentAwareDrawerFit(drawer, expect) {
|
|
|
154
161
|
clipped: labels.some((label) => label.clipped),
|
|
155
162
|
endGap: rightToLeft
|
|
156
163
|
? furthestLabel.logicalEnd - innerEnd
|
|
157
|
-
: innerEnd - furthestLabel.logicalEnd
|
|
164
|
+
: innerEnd - furthestLabel.logicalEnd,
|
|
165
|
+
iconLabelGaps: labels
|
|
166
|
+
.map((label) => label.iconLabelGap)
|
|
167
|
+
.filter((gap) => Number.isFinite(gap))
|
|
158
168
|
};
|
|
159
169
|
});
|
|
160
170
|
|
|
161
171
|
expect(fit).not.toBeNull();
|
|
162
172
|
expect(fit.clipped).toBe(false);
|
|
163
|
-
expect(fit.endGap).
|
|
164
|
-
expect(fit.
|
|
173
|
+
expect(Math.abs(fit.endGap - configuredSpacing)).toBeLessThanOrEqual(1.1);
|
|
174
|
+
expect(fit.iconLabelGaps.length).toBeGreaterThan(0);
|
|
175
|
+
for (const gap of fit.iconLabelGaps) {
|
|
176
|
+
expect(Math.abs(gap - configuredSpacing)).toBeLessThanOrEqual(1);
|
|
177
|
+
}
|
|
165
178
|
}
|
|
166
179
|
|
|
167
180
|
async function expectCentredRailNavigation(page, drawer, expect) {
|
|
@@ -6,7 +6,7 @@ export default {
|
|
|
6
6
|
name: "ShellLayout",
|
|
7
7
|
inheritAttrs: false,
|
|
8
8
|
setup(_, { attrs, slots }) {
|
|
9
|
-
// Keep drawerWidth, railWidth, and future shell props package-owned.
|
|
9
|
+
// Keep drawerWidth, railWidth, navigationItemSpacing, and future shell props package-owned.
|
|
10
10
|
return () => h(PackageShellLayout, attrs, slots);
|
|
11
11
|
}
|
|
12
12
|
};
|
|
@@ -166,6 +166,8 @@ 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
|
+
const configuredSpacing = Number(await drawer.getAttribute("data-navigation-item-spacing"));
|
|
170
|
+
assert.ok(Number.isFinite(configuredSpacing));
|
|
169
171
|
await expect.poll(async () => {
|
|
170
172
|
const configured = Number(await drawer.getAttribute("data-drawer-width"));
|
|
171
173
|
const box = await drawer.boundingBox();
|
|
@@ -180,12 +182,14 @@ async function assertDrawerFit(page) {
|
|
|
180
182
|
range.selectNodeContents(label);
|
|
181
183
|
const rect = range.getBoundingClientRect();
|
|
182
184
|
range.detach?.();
|
|
185
|
+
const iconRect = label.closest(".v-list-item")?.querySelector(".v-icon")?.getBoundingClientRect();
|
|
183
186
|
return {
|
|
184
187
|
clientHeight: label.clientHeight,
|
|
185
188
|
clientWidth: label.clientWidth,
|
|
186
189
|
rect,
|
|
187
190
|
scrollHeight: label.scrollHeight,
|
|
188
|
-
scrollWidth: label.scrollWidth
|
|
191
|
+
scrollWidth: label.scrollWidth,
|
|
192
|
+
iconLabelGap: iconRect ? rect.left - iconRect.right : null
|
|
189
193
|
};
|
|
190
194
|
});
|
|
191
195
|
const furthestEnd = Math.max(...labels.map((label) => label.rect.right));
|
|
@@ -195,11 +199,24 @@ async function assertDrawerFit(page) {
|
|
|
195
199
|
label.scrollHeight > label.clientHeight
|
|
196
200
|
)),
|
|
197
201
|
endGap: innerEnd - furthestEnd,
|
|
202
|
+
iconLabelGaps: labels
|
|
203
|
+
.map((label) => label.iconLabelGap)
|
|
204
|
+
.filter((gap) => Number.isFinite(gap)),
|
|
198
205
|
overflow: document.documentElement.scrollWidth - document.documentElement.clientWidth
|
|
199
206
|
};
|
|
200
207
|
});
|
|
201
208
|
assert.equal(result.clipped, false, "A drawer label was clipped or ellipsized.");
|
|
202
|
-
assert.ok(
|
|
209
|
+
assert.ok(
|
|
210
|
+
Math.abs(result.endGap - configuredSpacing) <= 1.1,
|
|
211
|
+
`Drawer label end gap was ${result.endGap}px; expected ${configuredSpacing}px.`
|
|
212
|
+
);
|
|
213
|
+
assert.ok(result.iconLabelGaps.length > 0, "Drawer links exposed no measurable icon/label gap.");
|
|
214
|
+
for (const gap of result.iconLabelGaps) {
|
|
215
|
+
assert.ok(
|
|
216
|
+
Math.abs(gap - configuredSpacing) <= 1,
|
|
217
|
+
`Drawer icon/label gap was ${gap}px; expected ${configuredSpacing}px.`
|
|
218
|
+
);
|
|
219
|
+
}
|
|
203
220
|
assert.ok(result.overflow <= 1, `Page overflowed horizontally by ${result.overflow}px.`);
|
|
204
221
|
}
|
|
205
222
|
|
|
@@ -282,6 +299,23 @@ test("shell-web adaptive navigation passes package-owned browser contracts", {
|
|
|
282
299
|
await assertDrawerFit(railPage);
|
|
283
300
|
await assertRailKeyboardAndSelection(railPage);
|
|
284
301
|
await railPage.close();
|
|
302
|
+
|
|
303
|
+
const configuredPage = await context.newPage();
|
|
304
|
+
await configuredPage.setViewportSize({ width: 1280, height: 900 });
|
|
305
|
+
await configuredPage.goto(
|
|
306
|
+
"/w/acme/admin/bookings?theme=light&railWidth=64&navigationItemSpacing=8"
|
|
307
|
+
);
|
|
308
|
+
const configuredDrawer = configuredPage.getByTestId("jskit-shell-drawer");
|
|
309
|
+
await expect(configuredDrawer).toHaveAttribute("data-navigation-item-spacing", "8");
|
|
310
|
+
await assertDrawerFit(configuredPage);
|
|
311
|
+
await configuredPage.getByTestId("jskit-shell-nav-toggle").click();
|
|
312
|
+
await expect(configuredDrawer).toHaveAttribute("data-presentation", "rail");
|
|
313
|
+
await expect(configuredDrawer).toHaveAttribute("data-rail-width", "64");
|
|
314
|
+
await expect.poll(async () => {
|
|
315
|
+
const box = await configuredDrawer.boundingBox();
|
|
316
|
+
return box ? Math.abs(box.width - 64) : Number.POSITIVE_INFINITY;
|
|
317
|
+
}).toBeLessThanOrEqual(1);
|
|
318
|
+
await configuredPage.close();
|
|
285
319
|
await context.close();
|
|
286
320
|
} finally {
|
|
287
321
|
await browser?.close();
|
package/test/drawerWidth.test.js
CHANGED
|
@@ -2,29 +2,32 @@ import assert from "node:assert/strict";
|
|
|
2
2
|
import test from "node:test";
|
|
3
3
|
import {
|
|
4
4
|
DEFAULT_SHELL_DRAWER_WIDTH,
|
|
5
|
+
DEFAULT_SHELL_NAVIGATION_ITEM_SPACING,
|
|
5
6
|
DEFAULT_SHELL_RAIL_WIDTH,
|
|
6
7
|
normalizeShellDrawerWidth,
|
|
8
|
+
normalizeShellNavigationItemSpacing,
|
|
7
9
|
normalizeShellRailWidth,
|
|
8
10
|
resolveContentAwareDrawerWidth
|
|
9
11
|
} from "../src/client/support/drawerWidth.js";
|
|
10
12
|
|
|
11
|
-
test("content-aware drawer width includes the rendered label start, width, and
|
|
13
|
+
test("content-aware drawer width includes the rendered label start, width, and balanced end spacing", () => {
|
|
12
14
|
assert.equal(
|
|
13
15
|
resolveContentAwareDrawerWidth([
|
|
14
16
|
{ logicalStart: 72, textWidth: 40 },
|
|
15
17
|
{ logicalStart: 72, textWidth: 78 }
|
|
16
18
|
]),
|
|
17
|
-
|
|
19
|
+
162
|
|
18
20
|
);
|
|
19
21
|
assert.equal(
|
|
20
22
|
resolveContentAwareDrawerWidth([
|
|
21
23
|
{ logicalStart: 72, textWidth: 99.015625 }
|
|
22
24
|
]),
|
|
23
|
-
|
|
25
|
+
183.25
|
|
24
26
|
);
|
|
25
27
|
});
|
|
26
28
|
|
|
27
29
|
test("drawer and rail widths have stable defaults and bounded overrides", () => {
|
|
30
|
+
assert.equal(DEFAULT_SHELL_RAIL_WIDTH, 80);
|
|
28
31
|
assert.equal(normalizeShellDrawerWidth(null), DEFAULT_SHELL_DRAWER_WIDTH);
|
|
29
32
|
assert.equal(normalizeShellDrawerWidth(180), 180);
|
|
30
33
|
assert.equal(normalizeShellDrawerWidth(1000), 360);
|
|
@@ -33,6 +36,14 @@ test("drawer and rail widths have stable defaults and bounded overrides", () =>
|
|
|
33
36
|
assert.equal(normalizeShellRailWidth(20), 48);
|
|
34
37
|
});
|
|
35
38
|
|
|
39
|
+
test("navigation item spacing has a Material-aligned default and bounded overrides", () => {
|
|
40
|
+
assert.equal(DEFAULT_SHELL_NAVIGATION_ITEM_SPACING, 12);
|
|
41
|
+
assert.equal(normalizeShellNavigationItemSpacing(null), DEFAULT_SHELL_NAVIGATION_ITEM_SPACING);
|
|
42
|
+
assert.equal(normalizeShellNavigationItemSpacing(14), 14);
|
|
43
|
+
assert.equal(normalizeShellNavigationItemSpacing(2), 8);
|
|
44
|
+
assert.equal(normalizeShellNavigationItemSpacing(100), 24);
|
|
45
|
+
});
|
|
46
|
+
|
|
36
47
|
test("content-aware width ignores invalid measurements and uses a bounded fallback", () => {
|
|
37
48
|
assert.equal(
|
|
38
49
|
resolveContentAwareDrawerWidth([
|
|
@@ -29,13 +29,15 @@ test("adaptive shell smoke follows rendered layout state and waits for drawer tr
|
|
|
29
29
|
const source = await readFile(path.join(PACKAGE_ROOT, "src/test/adaptiveShellSmoke.js"), "utf8");
|
|
30
30
|
|
|
31
31
|
assert.match(source, /data-layout/u);
|
|
32
|
+
assert.match(source, /data-navigation-item-spacing/u);
|
|
32
33
|
assert.match(source, /data-rail-width/u);
|
|
33
34
|
assert.match(source, /expect\.poll/u);
|
|
34
35
|
assert.match(source, /toBeFocused/u);
|
|
35
36
|
assert.match(source, /v-navigation-drawer__scrim/u);
|
|
36
37
|
assert.match(source, /page\.keyboard\.press\("Escape"\)[\s\S]*data-presentation", "drawer"/u);
|
|
37
38
|
assert.match(source, /iconBox\.x \+ iconBox\.width \/ 2/u);
|
|
38
|
-
assert.match(source, /endGap
|
|
39
|
+
assert.match(source, /fit\.endGap - configuredSpacing/u);
|
|
40
|
+
assert.match(source, /fit\.iconLabelGaps/u);
|
|
39
41
|
assert.doesNotMatch(source, /viewport\.name === "compact"/u);
|
|
40
42
|
assert.doesNotMatch(source, /await new Promise.*setTimeout|waitForTimeout/u);
|
|
41
43
|
});
|
|
@@ -100,13 +100,16 @@ test("shell-web shell layout registers navigation at the app layout level", asyn
|
|
|
100
100
|
assert.match(source, /:rail="drawerPresentation\.rail"/);
|
|
101
101
|
assert.match(source, /drawerWidth/);
|
|
102
102
|
assert.match(source, /railWidth/);
|
|
103
|
+
assert.match(source, /navigationItemSpacing/);
|
|
103
104
|
assert.match(source, /:width="resolvedDrawerWidth"/);
|
|
104
105
|
assert.match(source, /:rail-width="resolvedRailWidth"/);
|
|
105
106
|
assert.match(source, /data-drawer-width="resolvedDrawerWidth"/);
|
|
107
|
+
assert.match(source, /data-navigation-item-spacing="resolvedNavigationItemSpacing"/);
|
|
106
108
|
assert.match(source, /data-rail-width="resolvedRailWidth"/);
|
|
107
109
|
assert.match(source, /data-layout="layoutClass"/);
|
|
108
110
|
assert.match(source, /measureDrawerContentWidth/);
|
|
109
|
-
assert.match(source, /
|
|
111
|
+
assert.match(source, /DEFAULT_SHELL_NAVIGATION_ITEM_SPACING/);
|
|
112
|
+
assert.match(source, /:prepend-gap="resolvedNavigationItemSpacing"/);
|
|
110
113
|
assert.doesNotMatch(source, /:width="248"/);
|
|
111
114
|
assert.doesNotMatch(source, /:rail-width="80"/);
|
|
112
115
|
assert.match(source, /:model-value="drawerPresentation\.visible"/);
|
|
@@ -117,7 +120,7 @@ test("shell-web shell layout registers navigation at the app layout level", asyn
|
|
|
117
120
|
const template = await readFile(path.join(PACKAGE_DIR, "templates", "src", "components", "ShellLayout.vue"), "utf8");
|
|
118
121
|
|
|
119
122
|
assert.match(template, /PackageShellLayout from "@jskit-ai\/shell-web\/client\/components\/ShellLayout"/);
|
|
120
|
-
assert.match(template, /drawerWidth, railWidth, and future shell props package-owned/);
|
|
123
|
+
assert.match(template, /drawerWidth, railWidth, navigationItemSpacing, and future shell props package-owned/);
|
|
121
124
|
assert.match(template, /h\(PackageShellLayout, attrs, slots\)/);
|
|
122
125
|
assert.doesNotMatch(template, /ShellOutlet|ShellRouteTransition|useShellLayoutState|pointerdown|v-navigation-drawer|v-bottom-navigation/);
|
|
123
126
|
});
|