@ds-mo/ui 2.2.0 → 2.3.0

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.
Files changed (40) hide show
  1. package/dist/.build-stamp +1 -1
  2. package/dist/components/ds-app-shell.js +1 -1
  3. package/dist/components/ds-app-shell.js.map +1 -1
  4. package/dist/components/ds-bar-nav.js +1 -1
  5. package/dist/components/ds-bar-nav.js.map +1 -1
  6. package/dist/components/ds-button-unfilled-icon.js +1 -1
  7. package/dist/components/ds-checkbox.js +1 -1
  8. package/dist/components/ds-field.js +1 -1
  9. package/dist/components/ds-field.js.map +1 -1
  10. package/dist/components/ds-panel-nav.js +1 -1
  11. package/dist/components/ds-panel-nav.js.map +1 -1
  12. package/dist/components/ds-panel-tools.js +1 -1
  13. package/dist/components/ds-panel-tools.js.map +1 -1
  14. package/dist/components/ds-slider.js +1 -1
  15. package/dist/components/ds-tab-group-nav.js +1 -1
  16. package/dist/components/ds-toggle.js +1 -1
  17. package/dist/components/p-BTndnbR_.js +2 -0
  18. package/dist/components/p-BTndnbR_.js.map +1 -0
  19. package/dist/components/p-D4zKc1RW.js +2 -0
  20. package/dist/components/p-D4zKc1RW.js.map +1 -0
  21. package/dist/types/components/BarNav/BarNav.d.ts +4 -0
  22. package/dist/types/components/ButtonUnfilledIcon/ButtonUnfilledIcon.d.ts +5 -0
  23. package/dist/types/components/PanelNav/PanelNav.d.ts +7 -2
  24. package/dist/types/components/PanelTools/PanelTools.d.ts +4 -0
  25. package/dist/types/components/TabGroupNav/TabGroupNav.d.ts +22 -3
  26. package/dist/types/components.d.ts +39 -0
  27. package/package.json +1 -1
  28. package/src/angular/proxies.ts +11 -5
  29. package/src/react/ds-tab-group-nav.ts +8 -2
  30. package/src/wc/components/AppShell/AppShell.tsx +5 -7
  31. package/src/wc/components/BarNav/BarNav.tsx +33 -2
  32. package/src/wc/components/ButtonUnfilledIcon/ButtonUnfilledIcon.tsx +8 -0
  33. package/src/wc/components/PanelNav/PanelNav.tsx +116 -20
  34. package/src/wc/components/PanelTools/PanelTools.tsx +64 -14
  35. package/src/wc/components/TabGroupNav/TabGroupNav.tsx +164 -22
  36. package/src/wc/components.d.ts +39 -0
  37. package/dist/components/p-CSc44rQs.js +0 -2
  38. package/dist/components/p-CSc44rQs.js.map +0 -1
  39. package/dist/components/p-CZpCmCiq.js +0 -2
  40. package/dist/components/p-CZpCmCiq.js.map +0 -1
@@ -15,13 +15,19 @@ import { type DsTabGroupNavCustomEvent } from "@ds-mo/ui";
15
15
  import type { Components } from "@ds-mo/ui/dist/components";
16
16
  import { DsTabGroupNav as DsTabGroupNavElement, defineCustomElement as defineDsTabGroupNav } from "@ds-mo/ui/dist/components/ds-tab-group-nav.js";
17
17
 
18
- export type DsTabGroupNavEvents = { onDsChange: EventName<DsTabGroupNavCustomEvent<string>> };
18
+ export type DsTabGroupNavEvents = {
19
+ onDsChange: EventName<DsTabGroupNavCustomEvent<string>>,
20
+ onDsRovingExit: EventName<DsTabGroupNavCustomEvent<'start' | 'end'>>
21
+ };
19
22
 
20
23
  export const DsTabGroupNav: StencilReactComponent<DsTabGroupNavElement, DsTabGroupNavEvents, Components.DsTabGroupNav> = /*@__PURE__*/ createComponent<DsTabGroupNavElement, DsTabGroupNavEvents, Components.DsTabGroupNav>({
21
24
  tagName: 'ds-tab-group-nav',
22
25
  elementClass: DsTabGroupNavElement,
23
26
  // @ts-ignore - ignore potential React type mismatches between the Stencil Output Target and your project.
24
27
  react: React,
25
- events: { onDsChange: 'dsChange' } as DsTabGroupNavEvents,
28
+ events: {
29
+ onDsChange: 'dsChange',
30
+ onDsRovingExit: 'dsRovingExit'
31
+ } as DsTabGroupNavEvents,
26
32
  defineCustomElement: defineDsTabGroupNav
27
33
  });
@@ -322,17 +322,15 @@ export class AppShell {
322
322
  <slot name="panel" />
323
323
  </div>
324
324
  <div class="app-shell__main">
325
- <div class="app-shell__stack">
326
- <div class="app-shell__bar">
327
- <slot name="bar" />
328
- </div>
329
- <div class="app-shell__content">
330
- <slot />
331
- </div>
325
+ <div class="app-shell__bar">
326
+ <slot name="bar" />
332
327
  </div>
333
328
  <div class="app-shell__tools">
334
329
  <slot name="tools" />
335
330
  </div>
331
+ <div class="app-shell__content">
332
+ <slot />
333
+ </div>
336
334
  </div>
337
335
  </div>
338
336
  </Host>
@@ -86,6 +86,7 @@ export class BarNav {
86
86
  @State() private visibleTabs: BarNavTab[] = [];
87
87
  @State() private overflowTabs: BarNavTab[] = [];
88
88
  @State() private menuInitialFocusVisible = false;
89
+ @State() private overflowRovingFocused = false;
89
90
 
90
91
  private static readonly HOST_PROP_SYNC_BUDGET = 8;
91
92
  private static readonly INTRINSIC_WIDTH_RETRY_MAX = 3;
@@ -95,7 +96,7 @@ export class BarNav {
95
96
  private headerEl: HTMLElement | null = null;
96
97
  private triggerEl: (HTMLElement & { setFocus?: () => Promise<void> }) | null = null;
97
98
  private menuEl: HTMLDsMenuElement | null = null;
98
- private visibleTabGroupEl: QueryableHost | null = null;
99
+ private visibleTabGroupEl: HTMLDsTabGroupNavElement | null = null;
99
100
  private probeTabGroupEl: QueryableHost | null = null;
100
101
  private resizeObserver: ResizeObserver | null = null;
101
102
  private intrinsicWidthRetryCount = 0;
@@ -571,11 +572,32 @@ export class BarNav {
571
572
  this.menuOpen = true;
572
573
  }
573
574
 
575
+ private handleTabRovingExit(e: CustomEvent<'start' | 'end'>) {
576
+ if (e.detail !== 'end' || !this.hasOverflowTabs) return;
577
+ this.overflowRovingFocused = true;
578
+ void this.triggerEl?.setFocus?.();
579
+ }
580
+
581
+ private handleOverflowFocus = () => {
582
+ this.overflowRovingFocused = true;
583
+ };
584
+
585
+ private handleOverflowBlur = () => {
586
+ this.overflowRovingFocused = false;
587
+ };
588
+
574
589
  private handleTriggerKeyDown(e: KeyboardEvent) {
575
590
  if (!this.hasOverflowTabs) return;
576
591
 
577
592
  if (!this.menuOpen) {
578
- if (e.key === 'ArrowDown' || e.key === 'ArrowUp' || e.key === 'Enter' || e.key === ' ') {
593
+ if (e.key === 'ArrowLeft') {
594
+ e.preventDefault();
595
+ this.overflowRovingFocused = false;
596
+ void this.visibleTabGroupEl?.focusLastTab();
597
+ return;
598
+ }
599
+
600
+ if (e.key === 'Enter' || e.key === ' ') {
579
601
  e.preventDefault();
580
602
  this.toggleTabMenu({ focusVisible: true });
581
603
  }
@@ -645,7 +667,13 @@ export class BarNav {
645
667
  }}
646
668
  tabs={renderedTabs}
647
669
  value={this.effectiveValue}
670
+ selectionFollowsFocus={false}
671
+ rovingEnabled={!this.overflowRovingFocused}
648
672
  onDsChange={(e: Event) => this.handleTabChange(e)}
673
+ onDsRovingExit={(e: CustomEvent<'start' | 'end'>) => this.handleTabRovingExit(e)}
674
+ onFocusin={() => {
675
+ this.overflowRovingFocused = false;
676
+ }}
649
677
  />
650
678
  </div>
651
679
  )}
@@ -666,6 +694,7 @@ export class BarNav {
666
694
  icon="Ellipses"
667
695
  isActive={this.menuOpen}
668
696
  activeFill={false}
697
+ focusTabIndex={this.overflowRovingFocused ? 0 : -1}
669
698
  ref={(el?: HTMLDsButtonUnfilledIconElement) => {
670
699
  this.triggerEl = (el as (HTMLElement & { setFocus?: () => Promise<void> })) ?? null;
671
700
  }}
@@ -673,6 +702,8 @@ export class BarNav {
673
702
  expanded={this.menuOpen}
674
703
  aria-label="More tabs"
675
704
  onDsClick={() => this.toggleTabMenu({ focusVisible: false })}
705
+ onFocusin={this.handleOverflowFocus}
706
+ onFocusout={this.handleOverflowBlur}
676
707
  onKeyDown={(e: KeyboardEvent) => this.handleTriggerKeyDown(e)}
677
708
  />
678
709
  )}
@@ -46,6 +46,12 @@ export class ButtonUnfilledIcon {
46
46
  @Prop() haspopup: string | undefined;
47
47
  @Prop() pressed: boolean | undefined;
48
48
 
49
+ /**
50
+ * Native `tabindex` for roving keyboard groups in shell chrome.
51
+ * Omit for the default button tab stop (`0`).
52
+ */
53
+ @Prop({ attribute: 'tab-index' }) focusTabIndex?: number;
54
+
49
55
  @Event() dsClick!: EventEmitter<MouseEvent>;
50
56
  @Event() dsChange!: EventEmitter<boolean>;
51
57
 
@@ -67,6 +73,7 @@ export class ButtonUnfilledIcon {
67
73
 
68
74
  const cls: Record<string, boolean> = {
69
75
  'button-icon': true,
76
+ 'ds-focus-ring-inset': true,
70
77
  'button-icon--active': this.isActive,
71
78
  'button-icon--active-fill': this.isActive && this.activeFill,
72
79
  'button-icon--bordered': this.hasBorder,
@@ -87,6 +94,7 @@ export class ButtonUnfilledIcon {
87
94
  type={this.type}
88
95
  class={cls}
89
96
  disabled={this.inactive}
97
+ tabIndex={this.focusTabIndex ?? 0}
90
98
  aria-label={this.ariaLabel}
91
99
  aria-controls={this.controls}
92
100
  aria-expanded={this.expanded === undefined ? undefined : String(this.expanded)}
@@ -143,14 +143,14 @@ export class PanelNav {
143
143
  @Watch('groups')
144
144
  onGroupsChange(val: string | PanelNavGroup[]) {
145
145
  this.parsedGroups = parsePanelNavGroups(val);
146
- this.syncRovingIndex();
146
+ this.rovingIndex = 0;
147
147
  this.syncActiveFromUrl();
148
148
  }
149
149
 
150
150
  @Watch('activeId')
151
151
  @Watch('urlDerivedActiveId')
152
152
  onActiveIdChange() {
153
- this.syncRovingIndex();
153
+ /* Active route updates aria-current only — roving tab stop stays user-controlled. */
154
154
  }
155
155
 
156
156
  @Watch('currentUrl')
@@ -288,26 +288,112 @@ export class PanelNav {
288
288
  return this.parsedGroups.flatMap(g => g.items);
289
289
  }
290
290
 
291
- private syncRovingIndex() {
292
- const idx = this.getAllItems().findIndex(i => i.id === this.effectiveActiveId);
293
- this.rovingIndex = idx >= 0 ? idx : 0;
291
+ private getRovingTotal(): number {
292
+ return 1 + this.getAllItems().length + 2;
294
293
  }
295
294
 
296
- private handleItemKeyDown(e: KeyboardEvent, index: number) {
297
- const total = this.getAllItems().length;
295
+ private getFooterRovingIndex(): number {
296
+ return 1 + this.getAllItems().length;
297
+ }
298
+
299
+ private getUserRovingIndex(): number {
300
+ return this.getFooterRovingIndex() + 1;
301
+ }
302
+
303
+ private getRovingElement(index: number): HTMLElement | null {
304
+ const itemCount = this.getAllItems().length;
305
+ if (index === 0) {
306
+ return this.el.querySelector('.panel-nav__header-btn');
307
+ }
308
+ if (index >= 1 && index <= itemCount) {
309
+ const items = this.el.querySelectorAll<HTMLElement>('.panel-nav__body .panel-nav__item');
310
+ return items[index - 1] ?? null;
311
+ }
312
+ if (index === this.getFooterRovingIndex()) {
313
+ return this.el.querySelector('.panel-nav__footer-btn .button-icon');
314
+ }
315
+ if (index === this.getUserRovingIndex()) {
316
+ return this.el.querySelector('.panel-nav__footer-user');
317
+ }
318
+ return null;
319
+ }
320
+
321
+ private focusRovingAt(index: number) {
322
+ this.rovingIndex = index;
323
+ this.getRovingElement(index)?.focus({ preventScroll: true });
324
+ }
325
+
326
+ private activateRovingIndex(index: number) {
327
+ const itemCount = this.getAllItems().length;
328
+ const items = this.getAllItems();
329
+ if (index === 0) {
330
+ this.handleToggle();
331
+ return;
332
+ }
333
+ if (index >= 1 && index <= itemCount) {
334
+ this.handleItemClick(items[index - 1].id);
335
+ return;
336
+ }
337
+ if (index === this.getFooterRovingIndex()) {
338
+ this.handleFooterAction();
339
+ return;
340
+ }
341
+ if (index === this.getUserRovingIndex()) {
342
+ const anchor = this.el.querySelector(`#${PANEL_NAV_USER_MENU_ANCHOR_ID}`) as HTMLElement | null;
343
+ if (anchor) this.dsNavUserAction.emit({ anchor });
344
+ }
345
+ }
346
+
347
+ private handleRovingKeyDown(e: KeyboardEvent, index: number) {
348
+ if (e.key === 'Enter' || e.key === ' ') {
349
+ e.preventDefault();
350
+ this.activateRovingIndex(index);
351
+ return;
352
+ }
353
+
354
+ const total = this.getRovingTotal();
355
+ const footerIdx = this.getFooterRovingIndex();
356
+ const userIdx = this.getUserRovingIndex();
298
357
  let next: number;
358
+
299
359
  switch (e.key) {
300
- case 'ArrowDown': e.preventDefault(); next = (index + 1) % total; break;
301
- case 'ArrowUp': e.preventDefault(); next = (index - 1 + total) % total; break;
302
- case 'Home': e.preventDefault(); next = 0; break;
303
- case 'End': e.preventDefault(); next = total - 1; break;
304
- default: return;
360
+ case 'ArrowDown':
361
+ e.preventDefault();
362
+ if (index === footerIdx) next = userIdx;
363
+ else if (index === userIdx) next = 0;
364
+ else next = (index + 1) % total;
365
+ break;
366
+ case 'ArrowUp':
367
+ e.preventDefault();
368
+ if (index === 0) next = userIdx;
369
+ else if (index === userIdx) next = footerIdx;
370
+ else next = (index - 1 + total) % total;
371
+ break;
372
+ case 'ArrowRight':
373
+ if (index === footerIdx) {
374
+ e.preventDefault();
375
+ next = userIdx;
376
+ } else return;
377
+ break;
378
+ case 'ArrowLeft':
379
+ if (index === userIdx) {
380
+ e.preventDefault();
381
+ next = footerIdx;
382
+ } else return;
383
+ break;
384
+ case 'Home':
385
+ e.preventDefault();
386
+ next = 0;
387
+ break;
388
+ case 'End':
389
+ e.preventDefault();
390
+ next = userIdx;
391
+ break;
392
+ default:
393
+ return;
305
394
  }
306
- this.rovingIndex = next;
307
- const items = Array.from(
308
- this.el.querySelectorAll<HTMLElement>('.panel-nav__body .panel-nav__item')
309
- );
310
- items[next]?.focus();
395
+
396
+ this.focusRovingAt(next);
311
397
  }
312
398
 
313
399
  private checkScroll() {
@@ -411,6 +497,7 @@ export class PanelNav {
411
497
 
412
498
  private renderNavItem(item: PanelNavItem, idx: number, collapsed: boolean) {
413
499
  const isActive = item.id === this.effectiveActiveId;
500
+ const rovingPosition = idx + 1;
414
501
 
415
502
  const itemContent = [
416
503
  <span class="panel-nav__item-icon">
@@ -446,10 +533,10 @@ export class PanelNav {
446
533
  },
447
534
  'aria-current': isActive ? ('page' as const) : undefined,
448
535
  title: collapsed ? item.label : undefined,
449
- tabIndex: idx === this.rovingIndex ? 0 : -1,
536
+ tabIndex: rovingPosition === this.rovingIndex ? 0 : -1,
450
537
  onClick: () => this.handleItemClick(item.id),
451
- onKeyDown: (e: KeyboardEvent) => this.handleItemKeyDown(e, idx),
452
- onFocus: () => { this.rovingIndex = idx; },
538
+ onKeyDown: (e: KeyboardEvent) => this.handleRovingKeyDown(e, rovingPosition),
539
+ onFocus: () => { this.rovingIndex = rovingPosition; },
453
540
  };
454
541
 
455
542
  const useAnchor = this.routerMode === 'anchor' && item.href;
@@ -487,7 +574,10 @@ export class PanelNav {
487
574
  <button
488
575
  type="button"
489
576
  class="panel-nav__header-btn ds-focus-ring-inset"
577
+ tabIndex={this.rovingIndex === 0 ? 0 : -1}
490
578
  onClick={() => this.handleToggle()}
579
+ onKeyDown={(e: KeyboardEvent) => this.handleRovingKeyDown(e, 0)}
580
+ onFocus={() => { this.rovingIndex = 0; }}
491
581
  aria-label={collapsed ? 'Expand navigation' : 'Collapse navigation'}
492
582
  aria-expanded={collapsed ? 'false' : 'true'}
493
583
  >
@@ -543,9 +633,12 @@ export class PanelNav {
543
633
  class="panel-nav__footer-btn"
544
634
  icon={isDashboardChrome ? 'Gear' : 'Dashboard'}
545
635
  activeFill={false}
636
+ focusTabIndex={this.rovingIndex === this.getFooterRovingIndex() ? 0 : -1}
546
637
  title={isDashboardChrome ? 'Settings' : 'Dashboard'}
547
638
  aria-label={isDashboardChrome ? 'Open settings' : 'Go to dashboard'}
548
639
  onDsClick={() => this.handleFooterAction()}
640
+ onKeyDown={(e: KeyboardEvent) => this.handleRovingKeyDown(e, this.getFooterRovingIndex())}
641
+ onFocusin={() => { this.rovingIndex = this.getFooterRovingIndex(); }}
549
642
  />
550
643
 
551
644
  {/* Right user — label fades like nav items; right icon cross-fades chevron ↔ circle+initial */}
@@ -553,8 +646,11 @@ export class PanelNav {
553
646
  type="button"
554
647
  id={PANEL_NAV_USER_MENU_ANCHOR_ID}
555
648
  class="panel-nav__item panel-nav__footer-user ds-focus-ring-inset"
649
+ tabIndex={this.rovingIndex === this.getUserRovingIndex() ? 0 : -1}
556
650
  aria-label={collapsed ? `User: ${userName}` : `User menu for ${userName}`}
557
651
  onClick={(e) => this.handleUserAction(e)}
652
+ onKeyDown={(e: KeyboardEvent) => this.handleRovingKeyDown(e, this.getUserRovingIndex())}
653
+ onFocus={() => { this.rovingIndex = this.getUserRovingIndex(); }}
558
654
  >
559
655
  <span class="panel-nav__item-label panel-nav__footer-user-label">
560
656
  <span class="panel-nav__item-label-text">
@@ -51,12 +51,21 @@ export class PanelTools {
51
51
  /** Suppresses width transition until the host has painted its initial open state. */
52
52
  @State() private readyForMotion = false;
53
53
 
54
+ @State() private rovingIndex = 0;
55
+
54
56
  private motionEnableGeneration = 0;
55
57
 
56
58
  private get railItems(): PanelToolsItem[] {
57
59
  return parsePanelToolsItems(this.items, this.itemsJson);
58
60
  }
59
61
 
62
+ private get orderedRailItems(): PanelToolsItem[] {
63
+ const railItems = this.railItems;
64
+ const headerItem = railItems.find(item => item.id === PANEL_TOOLS_PRIMARY_TOOL_ID);
65
+ const bodyItems = railItems.filter(item => item.id !== PANEL_TOOLS_PRIMARY_TOOL_ID);
66
+ return headerItem ? [headerItem, ...bodyItems] : bodyItems;
67
+ }
68
+
60
69
  disconnectedCallback() {
61
70
  this.el.removeEventListener('transitionend', this.handleTransitionEnd);
62
71
  this.motionEnableGeneration += 1;
@@ -102,6 +111,7 @@ export class PanelTools {
102
111
  @Watch('items')
103
112
  @Watch('itemsJson')
104
113
  itemsChanged() {
114
+ this.rovingIndex = 0;
105
115
  this.deferMotionEnable();
106
116
  }
107
117
 
@@ -142,7 +152,40 @@ export class PanelTools {
142
152
  this.dsToolChange.emit({ id, selected });
143
153
  };
144
154
 
145
- private renderRailAction(item: PanelToolsItem) {
155
+ private focusRailAt(index: number) {
156
+ const items = this.orderedRailItems;
157
+ if (!items.length) return;
158
+ const bounded = ((index % items.length) + items.length) % items.length;
159
+ this.rovingIndex = bounded;
160
+ const actions = Array.from(
161
+ this.el.querySelectorAll<HTMLElement>('.panel-tools__rail-action .button-icon'),
162
+ );
163
+ actions[bounded]?.focus({ preventScroll: true });
164
+ }
165
+
166
+ private handleRailKeyDown = (e: KeyboardEvent, index: number) => {
167
+ const items = this.orderedRailItems;
168
+ if (!items.length) return;
169
+
170
+ if (e.key === 'Enter' || e.key === ' ') {
171
+ e.preventDefault();
172
+ this.handleToolChange(items[index].id);
173
+ return;
174
+ }
175
+
176
+ if (e.key === 'ArrowDown') {
177
+ e.preventDefault();
178
+ this.focusRailAt(index + 1);
179
+ return;
180
+ }
181
+
182
+ if (e.key === 'ArrowUp') {
183
+ e.preventDefault();
184
+ this.focusRailAt(index - 1);
185
+ }
186
+ };
187
+
188
+ private renderRailAction(item: PanelToolsItem, index: number) {
146
189
  return (
147
190
  <ds-button-unfilled-icon
148
191
  key={item.id}
@@ -152,18 +195,21 @@ export class PanelTools {
152
195
  activeFill={false}
153
196
  dot={item.dot ?? false}
154
197
  inactive={item.inactive}
198
+ focusTabIndex={index === this.rovingIndex ? 0 : -1}
155
199
  aria-label={item.ariaLabel ?? PANEL_TOOLS_LABELS[item.id]}
156
200
  pressed={this.isRailSelected(item.id)}
157
- onDsChange={() => this.handleToolChange(item.id)}
201
+ onFocusin={() => { this.rovingIndex = index; }}
202
+ onKeyDown={(e: KeyboardEvent) => this.handleRailKeyDown(e, index)}
203
+ onDsClick={() => this.handleToolChange(item.id)}
158
204
  />
159
205
  );
160
206
  }
161
207
 
162
208
  render() {
163
209
  const headerLabel = this.headerLabel();
164
- const railItems = this.railItems;
165
- const headerItem = railItems.find(item => item.id === PANEL_TOOLS_PRIMARY_TOOL_ID);
166
- const bodyItems = railItems.filter(item => item.id !== PANEL_TOOLS_PRIMARY_TOOL_ID);
210
+ const orderedRailItems = this.orderedRailItems;
211
+ const headerItem = orderedRailItems.find(item => item.id === PANEL_TOOLS_PRIMARY_TOOL_ID);
212
+ const bodyItems = orderedRailItems.filter(item => item.id !== PANEL_TOOLS_PRIMARY_TOOL_ID);
167
213
  const showDrawerChrome = this.isDrawerPresent();
168
214
  const drawerResting = panelToolsDrawerResting(this.open, this.motion);
169
215
 
@@ -181,6 +227,19 @@ export class PanelTools {
181
227
  aria-label="Tools"
182
228
  >
183
229
  <div class="panel-tools__layout">
230
+ <nav class="panel-tools__rail" aria-label="Tool shortcuts">
231
+ {headerItem ? (
232
+ <div class="panel-tools__rail-header">
233
+ {this.renderRailAction(headerItem, 0)}
234
+ </div>
235
+ ) : null}
236
+ <div class="panel-tools__rail-body">
237
+ {bodyItems.map((item, bodyIdx) =>
238
+ this.renderRailAction(item, headerItem ? bodyIdx + 1 : bodyIdx),
239
+ )}
240
+ </div>
241
+ </nav>
242
+
184
243
  <div
185
244
  class={{
186
245
  'panel-tools__drawer': true,
@@ -247,15 +306,6 @@ export class PanelTools {
247
306
  </div>
248
307
  </div>
249
308
  </div>
250
-
251
- <nav class="panel-tools__rail" aria-label="Tool shortcuts">
252
- {headerItem ? (
253
- <div class="panel-tools__rail-header">{this.renderRailAction(headerItem)}</div>
254
- ) : null}
255
- <div class="panel-tools__rail-body">
256
- {bodyItems.map(item => this.renderRailAction(item))}
257
- </div>
258
- </nav>
259
309
  </div>
260
310
  </Host>
261
311
  );