@ds-mo/ui 1.11.1 → 1.11.3

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.
@@ -21,6 +21,7 @@ import {
21
21
  SHELL_GRADIENT_POSITION_PANEL_VAR,
22
22
  SHELL_GRADIENT_SIZE_VAR,
23
23
  buildShellRadialGradient,
24
+ readShellViewportDimensions,
24
25
  shellGradientPositionBar,
25
26
  shellGradientPositionPanel,
26
27
  shellGradientSize,
@@ -53,12 +54,21 @@ export class AppShell {
53
54
  private readonly panelNavTransition = new ChromeTransitionDepth();
54
55
  private readonly chromeSyncCoalescer = createRafCoalescer(() => this.syncChrome());
55
56
  private panelWidthTokens: PanelNavWidthTokens = { expandedPx: 0, collapsedPx: 0 };
56
- private cachedShellWidth = 0;
57
- private cachedShellHeight = 0;
57
+ private cachedViewportWidth = 0;
58
+ private cachedViewportHeight = 0;
59
+ private onWindowResize = () => {
60
+ if (this.panelNavTransition.isActive) return;
61
+ this.scheduleChromeSync();
62
+ };
63
+ private onVisualViewportChange = () => {
64
+ if (this.panelNavTransition.isActive) return;
65
+ this.scheduleChromeSync();
66
+ };
58
67
 
59
68
  componentDidLoad() {
60
69
  this.syncSlottedNavStyle();
61
70
  this.connectMetricsObserver();
71
+ this.connectViewportListeners();
62
72
  this.el.addEventListener(CHROME_TRANSITION_START, this.onChromeTransitionStart);
63
73
  this.el.addEventListener(CHROME_TRANSITION_END, this.onChromeTransitionEnd);
64
74
  requestAnimationFrame(() => {
@@ -70,6 +80,7 @@ export class AppShell {
70
80
  disconnectedCallback() {
71
81
  this.resizeObserver?.disconnect();
72
82
  this.resizeObserver = null;
83
+ this.disconnectViewportListeners();
73
84
  this.chromeSyncCoalescer.cancel();
74
85
  this.el.removeEventListener(CHROME_TRANSITION_START, this.onChromeTransitionStart);
75
86
  this.el.removeEventListener(CHROME_TRANSITION_END, this.onChromeTransitionEnd);
@@ -88,8 +99,9 @@ export class AppShell {
88
99
  if (readChromeTransitionSource(event) !== 'panel-nav') return;
89
100
 
90
101
  this.panelNavTransition.enter();
91
- this.cachedShellWidth = this.el.clientWidth;
92
- this.cachedShellHeight = this.el.clientHeight;
102
+ const viewport = readShellViewportDimensions();
103
+ this.cachedViewportWidth = viewport.width;
104
+ this.cachedViewportHeight = viewport.height;
93
105
  this.syncChrome();
94
106
  };
95
107
 
@@ -130,6 +142,30 @@ export class AppShell {
130
142
  if (panelWrap) this.resizeObserver.observe(panelWrap);
131
143
  }
132
144
 
145
+ private connectViewportListeners() {
146
+ if (typeof window === 'undefined') return;
147
+
148
+ window.addEventListener('resize', this.onWindowResize, { passive: true });
149
+
150
+ const visual = window.visualViewport;
151
+ if (visual) {
152
+ visual.addEventListener('resize', this.onVisualViewportChange, { passive: true });
153
+ visual.addEventListener('scroll', this.onVisualViewportChange, { passive: true });
154
+ }
155
+ }
156
+
157
+ private disconnectViewportListeners() {
158
+ if (typeof window === 'undefined') return;
159
+
160
+ window.removeEventListener('resize', this.onWindowResize);
161
+
162
+ const visual = window.visualViewport;
163
+ if (visual) {
164
+ visual.removeEventListener('resize', this.onVisualViewportChange);
165
+ visual.removeEventListener('scroll', this.onVisualViewportChange);
166
+ }
167
+ }
168
+
133
169
  private cachePanelWidthTokens() {
134
170
  const navRoot = this.el.querySelector('ds-panel-nav .panel-nav') as HTMLElement | null;
135
171
  if (!navRoot) return;
@@ -190,16 +226,16 @@ export class AppShell {
190
226
  return 0;
191
227
  }
192
228
 
193
- private resolveShellDimensions(): { width: number; height: number } {
194
- if (this.panelNavTransition.isActive && this.cachedShellWidth > 0) {
229
+ /** Fixed-attachment wash/grid size always the viewport, never the shell element box. */
230
+ private resolveViewportDimensions(): { width: number; height: number } {
231
+ if (this.panelNavTransition.isActive && this.cachedViewportWidth > 0) {
195
232
  return {
196
- width: this.cachedShellWidth,
197
- height: this.cachedShellHeight || this.el.clientHeight,
233
+ width: this.cachedViewportWidth,
234
+ height: this.cachedViewportHeight,
198
235
  };
199
236
  }
200
237
 
201
- const rect = this.el.getBoundingClientRect();
202
- return { width: rect.width, height: rect.height };
238
+ return readShellViewportDimensions();
203
239
  }
204
240
 
205
241
  private chromeLayerActive(): boolean {
@@ -228,7 +264,7 @@ export class AppShell {
228
264
  return;
229
265
  }
230
266
 
231
- const { width: shellWidth, height: shellHeight } = this.resolveShellDimensions();
267
+ const viewport = this.resolveViewportDimensions();
232
268
  const panelWidth = this.resolvePanelWidthPx(panelNav);
233
269
 
234
270
  const panelPosition = shellGradientPositionPanel();
@@ -241,9 +277,8 @@ export class AppShell {
241
277
  : buildShellRadialGradient();
242
278
 
243
279
  const size = shellGradientSize({
244
- width: shellWidth,
245
- height: shellHeight,
246
- panelWidth,
280
+ width: viewport.width,
281
+ height: viewport.height,
247
282
  });
248
283
  const opacity = SHELL_GRADIENT_OPACITY;
249
284
 
@@ -11,6 +11,7 @@ import {
11
11
  } from '@stencil/core';
12
12
  import type { NavChromeStyle } from '../../nav/nav-chrome';
13
13
  import type { MenuItemData } from '../Menu/menu-types';
14
+ import { isTabDivider } from '../TabGroup/tab-item-utils';
14
15
  import type { BarNavActionItem, BarNavTab } from './bar-nav-types';
15
16
  import {
16
17
  deriveBarNavValueFromUrl,
@@ -34,8 +35,8 @@ import {
34
35
  ChromeTransitionDepth,
35
36
  createRafCoalescer,
36
37
  readChromeTransitionSource,
38
+ readChromeTransitionPhase,
37
39
  } from '../../nav/chrome-transition';
38
- import { readCssVarWidthPx } from '../../nav/shell-chrome-metrics';
39
40
 
40
41
  @Component({
41
42
  tag: 'ds-bar-nav',
@@ -111,12 +112,12 @@ export class BarNav {
111
112
  private resizeObserver: ResizeObserver | null = null;
112
113
  private intrinsicWidthRetryCount = 0;
113
114
  private readonly panelNavTransition = new ChromeTransitionDepth();
115
+ private readonly panelToolsTransition = new ChromeTransitionDepth();
114
116
  private readonly overflowCoalescer = createRafCoalescer(() => {
115
117
  this.updateTabsCollapsed();
116
118
  this.updateTriggerLabelTruncation();
117
119
  });
118
120
  private chromeTransitionShell: HTMLElement | null = null;
119
- private toolsDrawerOpening = false;
120
121
  /** Matches `basePath` once tabs + URL are reconciled for the active section. */
121
122
  @State() private committedSection = '';
122
123
  /** Section waiting for URL + tabs to catch up after a cross-area navigation. */
@@ -137,6 +138,17 @@ export class BarNav {
137
138
  return !!getActiveTab(this.resolvedTabs, this.effectiveValue)?.dot;
138
139
  }
139
140
 
141
+ private digestTabsConfig(tabs: BarNavTab[]): string {
142
+ return tabs.map(t => (isTabDivider(t) ? '|' : t.id)).join(',');
143
+ }
144
+
145
+ private resetTabOverflowLayout() {
146
+ this.tabLayoutCommitted = false;
147
+ this.tabsCollapsed = false;
148
+ this.menuOpen = false;
149
+ this.intrinsicWidthRetryCount = 0;
150
+ }
151
+
140
152
  @Watch('tabs')
141
153
  @Watch('tabsJson')
142
154
  @Watch('actions')
@@ -194,7 +206,7 @@ export class BarNav {
194
206
  }
195
207
 
196
208
  componentDidRender() {
197
- if (this.panelNavTransition.isActive) {
209
+ if (this.chromeOverflowPaused()) {
198
210
  this.syncMenuAnchor();
199
211
  return;
200
212
  }
@@ -241,16 +253,17 @@ export class BarNav {
241
253
  return;
242
254
  }
243
255
  if (source === 'panel-tools') {
244
- this.toolsDrawerOpening = true;
245
- this.el.classList.add('bar-nav--tools-drawer-opening');
246
- if (this.resolvedTabs.length > 0 && !this.hideTabsForDetailRoute) {
247
- this.tabsCollapsed = true;
248
- this.menuOpen = false;
256
+ const phase = readChromeTransitionPhase(event) ?? 'opening';
257
+ if (phase === 'closing') {
258
+ this.panelToolsTransition.enter();
249
259
  }
250
- this.scheduleOverflowCheck();
251
260
  }
252
261
  };
253
262
 
263
+ private chromeOverflowPaused(): boolean {
264
+ return this.panelNavTransition.isActive || this.panelToolsTransition.isActive;
265
+ }
266
+
254
267
  private onChromeTransitionEnd = (event: Event) => {
255
268
  const source = readChromeTransitionSource(event);
256
269
  if (source === 'panel-nav') {
@@ -261,8 +274,7 @@ export class BarNav {
261
274
  return;
262
275
  }
263
276
  if (source === 'panel-tools') {
264
- this.toolsDrawerOpening = false;
265
- this.el.classList.remove('bar-nav--tools-drawer-opening');
277
+ this.panelToolsTransition.exit();
266
278
  this.scheduleOverflowCheck();
267
279
  }
268
280
  };
@@ -301,14 +313,14 @@ export class BarNav {
301
313
  if (typeof ResizeObserver === 'undefined' || !this.headerEl) return;
302
314
  this.resizeObserver?.disconnect();
303
315
  this.resizeObserver = new ResizeObserver(() => {
304
- if (this.panelNavTransition.isActive) return;
316
+ if (this.chromeOverflowPaused()) return;
305
317
  this.scheduleOverflowCheck();
306
318
  });
307
319
  this.resizeObserver.observe(this.headerEl);
308
320
  }
309
321
 
310
322
  private scheduleOverflowCheck() {
311
- if (this.panelNavTransition.isActive) return;
323
+ if (this.chromeOverflowPaused()) return;
312
324
  this.overflowCoalescer.schedule();
313
325
  }
314
326
 
@@ -366,20 +378,7 @@ export class BarNav {
366
378
  const available =
367
379
  this.headerEl.clientWidth - horizontalPadding - actionsWidth - spacing;
368
380
 
369
- return Math.max(0, available - this.toolsDrawerOpeningReservePx());
370
- }
371
-
372
- /** While the tools drawer is opening, reserve width not yet claimed by flex shrink. */
373
- private toolsDrawerOpeningReservePx(): number {
374
- if (!this.toolsDrawerOpening) return 0;
375
-
376
- const tools = this.el.closest('ds-app-shell')?.querySelector('ds-panel-tools') as HTMLElement | null;
377
- if (!tools) return 0;
378
-
379
- const drawerTarget = readCssVarWidthPx(tools, '--_panel-tools-drawer-width');
380
- const drawerNow =
381
- tools.querySelector('.panel-tools__drawer')?.getBoundingClientRect().width ?? 0;
382
- return Math.max(0, drawerTarget - drawerNow);
381
+ return Math.max(0, available);
383
382
  }
384
383
 
385
384
  private getTabsIntrinsicWidth(): number {
@@ -400,12 +399,19 @@ export class BarNav {
400
399
  this.menuOpen = false;
401
400
  }
402
401
  this.intrinsicWidthRetryCount = 0;
402
+ this.tabLayoutCommitted = true;
403
403
  return;
404
404
  }
405
405
 
406
406
  const intrinsicWidth = this.getTabsIntrinsicWidth();
407
407
  if (intrinsicWidth === 0) {
408
- this.scheduleIntrinsicWidthRetry();
408
+ if (this.intrinsicWidthRetryCount >= BarNav.INTRINSIC_WIDTH_RETRY_MAX) {
409
+ this.intrinsicWidthRetryCount = 0;
410
+ this.tabsCollapsed = false;
411
+ this.tabLayoutCommitted = true;
412
+ } else {
413
+ this.scheduleIntrinsicWidthRetry();
414
+ }
409
415
  return;
410
416
  }
411
417
  this.intrinsicWidthRetryCount = 0;
@@ -423,6 +429,8 @@ export class BarNav {
423
429
  this.menuOpen = false;
424
430
  }
425
431
  }
432
+
433
+ this.tabLayoutCommitted = true;
426
434
  }
427
435
 
428
436
  private syncMenuAnchor() {
@@ -469,6 +477,7 @@ export class BarNav {
469
477
  this.pendingSection = nextBasePath;
470
478
  this.resolvedTabs = [];
471
479
  this.resolvedActions = this.incomingActions();
480
+ this.resetTabOverflowLayout();
472
481
  return;
473
482
  }
474
483
 
@@ -486,11 +495,17 @@ export class BarNav {
486
495
  this.pendingSection = '';
487
496
  }
488
497
 
489
- this.resolvedTabs = this.incomingTabs();
498
+ const tabs = this.incomingTabs();
499
+ const tabsConfigChanged =
500
+ this.digestTabsConfig(tabs) !== this.digestTabsConfig(this.resolvedTabs);
501
+
502
+ this.resolvedTabs = tabs;
490
503
  this.resolvedActions = this.incomingActions();
491
504
  this.syncValueFromUrl();
492
505
  this.committedSection = nextBasePath;
493
- this.intrinsicWidthRetryCount = 0;
506
+ if (tabsConfigChanged) {
507
+ this.resetTabOverflowLayout();
508
+ }
494
509
  this.scheduleOverflowCheck();
495
510
  }
496
511
 
@@ -593,7 +608,8 @@ export class BarNav {
593
608
  'bar-nav': true,
594
609
  'bar-nav--dashboard': this.navStyle === 'dashboard',
595
610
  'bar-nav--settings': this.navStyle === 'settings',
596
- 'bar-nav--tabs-collapsed': hasTabs && this.tabsCollapsed,
611
+ 'bar-nav--tabs-collapsed':
612
+ hasTabs && this.tabLayoutCommitted && this.tabsCollapsed,
597
613
  }}
598
614
  ref={el => {
599
615
  this.headerEl = el as HTMLElement;
@@ -619,7 +635,7 @@ export class BarNav {
619
635
  </div>
620
636
  )}
621
637
 
622
- {hasTabs && !this.tabsCollapsed && (
638
+ {hasTabs && this.tabLayoutCommitted && !this.tabsCollapsed && (
623
639
  <div class="bar-nav__left">
624
640
  <ds-tab-group-nav
625
641
  key={`visible-${tabGroupKey}`}
@@ -634,11 +650,15 @@ export class BarNav {
634
650
  </div>
635
651
  )}
636
652
 
653
+ {hasTabs && !this.tabLayoutCommitted && (
654
+ <div class="bar-nav__tabs-pending" aria-hidden="true" />
655
+ )}
656
+
637
657
  {!hasTabs && this.heading && (
638
658
  <span class="bar-nav__heading text-body-medium-emphasis">{this.heading}</span>
639
659
  )}
640
660
 
641
- {hasTabs && this.tabsCollapsed && (
661
+ {hasTabs && this.tabLayoutCommitted && this.tabsCollapsed && (
642
662
  <button
643
663
  type="button"
644
664
  class={{
@@ -697,7 +717,10 @@ export class BarNav {
697
717
  </button>
698
718
  )}
699
719
 
700
- {hasTabs && this.tabsCollapsed && this.resolvedActions.length > 0 && (
720
+ {hasTabs &&
721
+ this.tabLayoutCommitted &&
722
+ this.tabsCollapsed &&
723
+ this.resolvedActions.length > 0 && (
701
724
  <div class="bar-nav__between" aria-hidden="true" />
702
725
  )}
703
726
 
@@ -719,7 +742,7 @@ export class BarNav {
719
742
 
720
743
  </header>
721
744
 
722
- {hasTabs && this.tabsCollapsed && (
745
+ {hasTabs && this.tabLayoutCommitted && this.tabsCollapsed && (
723
746
  <ds-menu
724
747
  ref={el => {
725
748
  this.menuEl = (el as HTMLDsMenuElement) ?? null;
@@ -82,7 +82,10 @@ export class PanelTools {
82
82
  openChanged(isOpen: boolean, wasOpen?: boolean) {
83
83
  if (this.readyForMotion && wasOpen !== undefined && wasOpen !== isOpen) {
84
84
  this.motion = isOpen ? 'opening' : 'closing';
85
- this.dsChromeTransitionStart.emit({ source: 'panel-tools' });
85
+ this.dsChromeTransitionStart.emit({
86
+ source: 'panel-tools',
87
+ phase: isOpen ? 'opening' : 'closing',
88
+ });
86
89
  }
87
90
  this.deferMotionEnable();
88
91
  }
@@ -182,6 +185,7 @@ export class PanelTools {
182
185
  'panel-tools__drawer--visible': showDrawerChrome,
183
186
  }}
184
187
  aria-hidden={showDrawerChrome ? null : 'true'}
188
+ inert={showDrawerChrome ? undefined : true}
185
189
  >
186
190
  <div class="panel-tools__drawer-surface">
187
191
  <header class="panel-tools__header">
@@ -7,6 +7,8 @@ export type ChromeTransitionSource = 'panel-nav' | 'panel-tools';
7
7
 
8
8
  export interface ChromeTransitionDetail {
9
9
  source: ChromeTransitionSource;
10
+ /** Panel-tools drawer motion direction; omitted for panel-nav. */
11
+ phase?: 'opening' | 'closing';
10
12
  }
11
13
 
12
14
  /** Reference-counted gate — used while panel-nav width is transitioning. */
@@ -54,3 +56,7 @@ export function createRafCoalescer(onFrame: () => void): RafCoalescer {
54
56
  export function readChromeTransitionSource(event: Event): ChromeTransitionSource | undefined {
55
57
  return (event as CustomEvent<ChromeTransitionDetail>).detail?.source;
56
58
  }
59
+
60
+ export function readChromeTransitionPhase(event: Event): ChromeTransitionDetail['phase'] {
61
+ return (event as CustomEvent<ChromeTransitionDetail>).detail?.phase;
62
+ }
@@ -34,8 +34,9 @@ export {
34
34
  shellGradientPositionBar,
35
35
  shellChromeSurfacePosition,
36
36
  shellChromeLayerActive,
37
+ readShellViewportDimensions,
37
38
  } from './shell-gradient';
38
- export type { ShellGradientLayout } from './shell-gradient';
39
+ export type { ShellGradientLayout, ShellViewportDimensions } from './shell-gradient';
39
40
  export type { ChromeTransitionDetail, ChromeTransitionSource } from './chrome-transition';
40
41
  export {
41
42
  CHROME_TRANSITION_END,
@@ -43,6 +44,7 @@ export {
43
44
  ChromeTransitionDepth,
44
45
  createRafCoalescer,
45
46
  readChromeTransitionSource,
47
+ readChromeTransitionPhase,
46
48
  } from './chrome-transition';
47
49
  export {
48
50
  barGradientPositionFromPanelWidth,
@@ -30,13 +30,35 @@ export function shellGradientImage(): string {
30
30
  return buildShellRadialGradient();
31
31
  }
32
32
 
33
+ export interface ShellViewportDimensions {
34
+ width: number;
35
+ height: number;
36
+ }
37
+
38
+ /**
39
+ * Viewport size for shell chrome with `background-attachment: fixed`.
40
+ * Must not use the `ds-app-shell` element box — the shell can be shorter or
41
+ * taller than the viewport when host height chains break or content overflows.
42
+ */
43
+ export function readShellViewportDimensions(win?: Window): ShellViewportDimensions {
44
+ const w = win ?? (typeof globalThis.window !== 'undefined' ? globalThis.window : undefined);
45
+ if (!w) return { width: 0, height: 0 };
46
+
47
+ const visual = w.visualViewport;
48
+ return {
49
+ width: Math.round(visual?.width ?? w.innerWidth),
50
+ height: Math.round(visual?.height ?? w.innerHeight),
51
+ };
52
+ }
53
+
33
54
  export interface ShellGradientLayout {
34
55
  width: number;
35
56
  height: number;
36
57
  panelWidth: number;
37
58
  }
38
59
 
39
- export function shellGradientSize(layout: ShellGradientLayout): string {
60
+ /** Pixel `background-size` for the fixed-attachment radial wash. */
61
+ export function shellGradientSize(layout: Pick<ShellGradientLayout, 'width' | 'height'>): string {
40
62
  return `${Math.round(layout.width)}px ${Math.round(layout.height)}px`;
41
63
  }
42
64
 
@@ -1,2 +0,0 @@
1
- const t="dsChromeTransitionStart";const n="dsChromeTransitionEnd";class e{constructor(){this.depth=0}enter(){this.depth+=1}exit(){this.depth=Math.max(0,this.depth-1)}get isActive(){return this.depth>0}}function i(t){let n=0;return{schedule(){if(n!==0)return;n=requestAnimationFrame((()=>{n=0;t()}))},cancel(){if(n!==0){cancelAnimationFrame(n);n=0}}}}function r(t){return t.detail?.source}function s(t,n){if(typeof document==="undefined")return 0;const e=document.createElement("div");e.style.cssText="position:absolute;visibility:hidden;pointer-events:none;height:0;width:var("+n+");";t.appendChild(e);const i=e.getBoundingClientRect().width;t.removeChild(e);return i}function a(t){return{expandedPx:s(t,"--_nav-width"),collapsedPx:s(t,"--_nav-width-collapsed")}}function o(t,n){if(n?.classList.contains("panel-nav--collapsed"))return true;const e=t?.collapsed;return e===true}function c(t,n){return n?t.collapsedPx:t.expandedPx}export{e as C,t as a,n as b,i as c,a as d,s as e,o as i,c as p,r};
2
- //# sourceMappingURL=p-1HlgCxGN.js.map
@@ -1 +0,0 @@
1
- {"version":3,"names":["CHROME_TRANSITION_START","CHROME_TRANSITION_END","ChromeTransitionDepth","constructor","this","depth","enter","exit","Math","max","isActive","createRafCoalescer","onFrame","rafId","schedule","requestAnimationFrame","cancel","cancelAnimationFrame","readChromeTransitionSource","event","detail","source","readCssVarWidthPx","context","cssVarName","document","probe","createElement","style","cssText","appendChild","width","getBoundingClientRect","removeChild","readPanelNavWidthTokens","navRoot","expandedPx","collapsedPx","isPanelNavCollapsed","panelNavHost","classList","contains","collapsedProp","collapsed","panelWidthPxFromTokens","tokens"],"sources":["src/wc/nav/chrome-transition.ts","src/wc/nav/shell-chrome-metrics.ts"],"sourcesContent":["/** Bubbling/composed events — `ds-app-shell` and `ds-bar-nav` coordinate during width motion. */\n\nexport const CHROME_TRANSITION_START = 'dsChromeTransitionStart';\nexport const CHROME_TRANSITION_END = 'dsChromeTransitionEnd';\n\nexport type ChromeTransitionSource = 'panel-nav' | 'panel-tools';\n\nexport interface ChromeTransitionDetail {\n source: ChromeTransitionSource;\n}\n\n/** Reference-counted gate — used while panel-nav width is transitioning. */\nexport class ChromeTransitionDepth {\n private depth = 0;\n\n enter(): void {\n this.depth += 1;\n }\n\n exit(): void {\n this.depth = Math.max(0, this.depth - 1);\n }\n\n get isActive(): boolean {\n return this.depth > 0;\n }\n}\n\nexport interface RafCoalescer {\n schedule(): void;\n cancel(): void;\n}\n\n/** Coalesce bursty layout work (ResizeObserver, prop churn) to one callback per frame. */\nexport function createRafCoalescer(onFrame: () => void): RafCoalescer {\n let rafId = 0;\n return {\n schedule() {\n if (rafId !== 0) return;\n rafId = requestAnimationFrame(() => {\n rafId = 0;\n onFrame();\n });\n },\n cancel() {\n if (rafId !== 0) {\n cancelAnimationFrame(rafId);\n rafId = 0;\n }\n },\n };\n}\n\nexport function readChromeTransitionSource(event: Event): ChromeTransitionSource | undefined {\n return (event as CustomEvent<ChromeTransitionDetail>).detail?.source;\n}\n","import { shellGradientPositionBar } from './shell-gradient';\n\n/** Read a `var(--token)` width in px using a hidden probe (works with calc() tokens). */\nexport function readCssVarWidthPx(context: HTMLElement, cssVarName: string): number {\n if (typeof document === 'undefined') return 0;\n\n const probe = document.createElement('div');\n probe.style.cssText =\n 'position:absolute;visibility:hidden;pointer-events:none;height:0;width:var(' +\n cssVarName +\n ');';\n context.appendChild(probe);\n const width = probe.getBoundingClientRect().width;\n context.removeChild(probe);\n return width;\n}\n\nexport interface PanelNavWidthTokens {\n expandedPx: number;\n collapsedPx: number;\n}\n\n/** Resolve panel-nav expanded/collapsed widths from scoped CSS vars on `.panel-nav`. */\nexport function readPanelNavWidthTokens(navRoot: HTMLElement): PanelNavWidthTokens {\n return {\n expandedPx: readCssVarWidthPx(navRoot, '--_nav-width'),\n collapsedPx: readCssVarWidthPx(navRoot, '--_nav-width-collapsed'),\n };\n}\n\nexport function isPanelNavCollapsed(\n panelNavHost: HTMLElement | null,\n navRoot: HTMLElement | null,\n): boolean {\n if (navRoot?.classList.contains('panel-nav--collapsed')) return true;\n const collapsedProp = (panelNavHost as (HTMLElement & { collapsed?: boolean }) | null)?.collapsed;\n return collapsedProp === true;\n}\n\nexport function panelWidthPxFromTokens(\n tokens: PanelNavWidthTokens,\n collapsed: boolean,\n): number {\n return collapsed ? tokens.collapsedPx : tokens.expandedPx;\n}\n\nexport function barGradientPositionFromPanelWidth(panelWidthPx: number): string {\n return shellGradientPositionBar(panelWidthPx);\n}\n"],"mappings":"AAEO,MAAMA,EAA0B,0BAChC,MAAMC,EAAwB,wB,MASxBC,EAAb,WAAAC,GACUC,KAAAC,MAAQ,C,CAEhB,KAAAC,GACEF,KAAKC,OAAS,C,CAGhB,IAAAE,GACEH,KAAKC,MAAQG,KAAKC,IAAI,EAAGL,KAAKC,MAAQ,E,CAGxC,YAAIK,GACF,OAAON,KAAKC,MAAQ,C,EAUlB,SAAUM,EAAmBC,GACjC,IAAIC,EAAQ,EACZ,MAAO,CACL,QAAAC,GACE,GAAID,IAAU,EAAG,OACjBA,EAAQE,uBAAsB,KAC5BF,EAAQ,EACRD,GAAS,G,EAGb,MAAAI,GACE,GAAIH,IAAU,EAAG,CACfI,qBAAqBJ,GACrBA,EAAQ,C,GAIhB,CAEM,SAAUK,EAA2BC,GACzC,OAAQA,EAA8CC,QAAQC,MAChE,CCpDM,SAAUC,EAAkBC,EAAsBC,GACtD,UAAWC,WAAa,YAAa,OAAO,EAE5C,MAAMC,EAAQD,SAASE,cAAc,OACrCD,EAAME,MAAMC,QACV,8EACAL,EACA,KACFD,EAAQO,YAAYJ,GACpB,MAAMK,EAAQL,EAAMM,wBAAwBD,MAC5CR,EAAQU,YAAYP,GACpB,OAAOK,CACT,CAQM,SAAUG,EAAwBC,GACtC,MAAO,CACLC,WAAYd,EAAkBa,EAAS,gBACvCE,YAAaf,EAAkBa,EAAS,0BAE5C,CAEM,SAAUG,EACdC,EACAJ,GAEA,GAAIA,GAASK,UAAUC,SAAS,wBAAyB,OAAO,KAChE,MAAMC,EAAiBH,GAAiEI,UACxF,OAAOD,IAAkB,IAC3B,CAEM,SAAUE,EACdC,EACAF,GAEA,OAAOA,EAAYE,EAAOR,YAAcQ,EAAOT,UACjD,Q","ignoreList":[]}