@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 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 :surface="surface" :surface-label="surfaceLabel">
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>
@@ -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.150",
4
+ version: "0.1.151",
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.150",
3
+ "version": "0.1.151",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "test": "node --test"
@@ -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: SHELL_DRAWER_LABEL_END_GAP + endBorderWidth
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-drawer-label-end-gap': `${SHELL_DRAWER_LABEL_END_GAP}px` }"
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 nav density="comfortable" class="pt-2">
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-drawer-label-end-gap) / 2);
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 SHELL_DRAWER_LABEL_END_GAP = 10;
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 = SHELL_DRAWER_LABEL_END_GAP,
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
- // Layout boxes expose integer client widths even when the rendered glyph
60
- // range is fractional. Round outward so the final glyph is never ellipsized
61
- // at the exact end-gap boundary.
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).toBeGreaterThanOrEqual(9);
164
- expect(fit.endGap).toBeLessThanOrEqual(11);
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(result.endGap >= 9 && result.endGap <= 11, `Drawer label end gap was ${result.endGap}px.`);
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();
@@ -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 10px end gap", () => {
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
- 160
19
+ 162
18
20
  );
19
21
  assert.equal(
20
22
  resolveContentAwareDrawerWidth([
21
23
  { logicalStart: 72, textWidth: 99.015625 }
22
24
  ]),
23
- 182
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\)\.toBeGreaterThanOrEqual\(9\)/u);
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, /SHELL_DRAWER_LABEL_END_GAP/);
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
  });