@ds-mo/ui 15.10.1 → 15.11.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 (155) hide show
  1. package/dist/.package-ready.json +1 -1
  2. package/dist/agent-patterns.json +1 -1
  3. package/dist/agent.json +13 -11
  4. package/dist/angular/ds-shell-app.d.ts +3 -8
  5. package/dist/angular/ds-shell-tools.d.ts +3 -8
  6. package/dist/components/ds-agent-questionnaire.js +1 -1
  7. package/dist/components/ds-agent-response.js +1 -1
  8. package/dist/components/ds-banner.js +1 -1
  9. package/dist/components/ds-bar-nav.js +1 -1
  10. package/dist/components/ds-bar-page-title.js +1 -1
  11. package/dist/components/ds-bar-title.js +1 -1
  12. package/dist/components/ds-bar-workflow.js +1 -1
  13. package/dist/components/ds-button-unfilled.js +1 -1
  14. package/dist/components/ds-calendar.js +1 -1
  15. package/dist/components/ds-card-chart.js +1 -1
  16. package/dist/components/ds-card-overview.js +1 -1
  17. package/dist/components/ds-card-overview.js.map +1 -1
  18. package/dist/components/ds-card-setting.js +1 -1
  19. package/dist/components/ds-chart.js +1 -1
  20. package/dist/components/ds-chart.js.map +1 -1
  21. package/dist/components/ds-code-block.js +1 -1
  22. package/dist/components/ds-data-customize.js +1 -1
  23. package/dist/components/ds-data-filter.js +1 -1
  24. package/dist/components/ds-data-group.js +1 -1
  25. package/dist/components/ds-data-preferences.js +1 -1
  26. package/dist/components/ds-data-saved-views.js +1 -1
  27. package/dist/components/ds-data-search.js +1 -1
  28. package/dist/components/ds-data-sort.js +1 -1
  29. package/dist/components/ds-filter-menu.js +1 -1
  30. package/dist/components/ds-input-date.js +1 -1
  31. package/dist/components/ds-input-time.js +1 -1
  32. package/dist/components/ds-input.js +1 -1
  33. package/dist/components/ds-markdown.js +1 -1
  34. package/dist/components/ds-menu.js +1 -1
  35. package/dist/components/ds-message-actions.js +1 -1
  36. package/dist/components/ds-message-composer.js +1 -1
  37. package/dist/components/ds-message-scroller.js +1 -1
  38. package/dist/components/ds-mobile-bar-nav.js +1 -1
  39. package/dist/components/ds-mobile-header.js +1 -1
  40. package/dist/components/ds-mobile-section-switcher.js +1 -1
  41. package/dist/components/ds-mobile-sheet-nav.js +1 -1
  42. package/dist/components/ds-modal.js +1 -1
  43. package/dist/components/ds-pagination.js +1 -1
  44. package/dist/components/ds-panel-nav.js +1 -1
  45. package/dist/components/ds-panel-tool-header.js +1 -1
  46. package/dist/components/ds-panel-tool-search.js +1 -1
  47. package/dist/components/ds-panel-tools.js +1 -1
  48. package/dist/components/ds-scroll-overlay.js +1 -1
  49. package/dist/components/ds-select.js +1 -1
  50. package/dist/components/ds-shell-app.js +1 -1
  51. package/dist/components/ds-shell-app.js.map +1 -1
  52. package/dist/components/ds-shell-page.js +1 -1
  53. package/dist/components/ds-shell-tools.js +1 -1
  54. package/dist/components/ds-table.js +1 -1
  55. package/dist/components/ds-table.js.map +1 -1
  56. package/dist/components/ds-toast.js +1 -1
  57. package/dist/components/ds-toast.js.map +1 -1
  58. package/dist/components/ds-tooltip.js +1 -1
  59. package/dist/components/{p-PNY7auMh.js → p-B5rN3slq.js} +2 -2
  60. package/dist/components/{p-kQ6wvk-r.js → p-B9Q7fXxW.js} +2 -2
  61. package/dist/components/p-BNnw_O4e.js +2 -0
  62. package/dist/components/p-BNnw_O4e.js.map +1 -0
  63. package/dist/components/{p-BgpzSEWv.js → p-BT6xQogY.js} +2 -2
  64. package/dist/components/{p-YmJYwnhW.js → p-Ba-dSshd.js} +2 -2
  65. package/dist/components/{p-YmJYwnhW.js.map → p-Ba-dSshd.js.map} +1 -1
  66. package/dist/components/p-BkajUPiA.js +2 -0
  67. package/dist/components/p-BkajUPiA.js.map +1 -0
  68. package/dist/components/{p-lJeAJKa5.js → p-BqIRExej.js} +2 -2
  69. package/dist/components/{p-BtSEQ-FW.js → p-C8D853vs.js} +2 -2
  70. package/dist/components/{p-CHYYRdRJ.js → p-CEB8RFih.js} +2 -2
  71. package/dist/components/{p-DuCCuJKI.js → p-CGKjjJZm.js} +2 -2
  72. package/dist/components/{p-2ZV8JA88.js → p-CMrHRjjH.js} +2 -2
  73. package/dist/components/{p-DwuqJhR5.js → p-CPNA8raE.js} +2 -2
  74. package/dist/components/{p-ioGH5SoA.js → p-CVYjLzYI.js} +2 -2
  75. package/dist/components/p-CVYjLzYI.js.map +1 -0
  76. package/dist/components/p-CW-7vLUB.js +2 -0
  77. package/dist/components/p-CW-7vLUB.js.map +1 -0
  78. package/dist/components/{p-BrB05_X8.js → p-CaNMVLhm.js} +2 -2
  79. package/dist/components/{p-hmvu1lmP.js → p-Cxk1A7Sx.js} +2 -2
  80. package/dist/components/p-Cxk1A7Sx.js.map +1 -0
  81. package/dist/components/{p-BjLGayCj.js → p-D-cZ6cRI.js} +2 -2
  82. package/dist/components/{p-DQeUT9o2.js → p-D4r6oWyQ.js} +2 -2
  83. package/dist/components/{p-DoRgzvup.js → p-DGDF4NMV.js} +2 -2
  84. package/dist/components/{p-DSpKpoSG.js → p-DKG26pp5.js} +2 -2
  85. package/dist/components/p-DfphSI9S.js +2 -0
  86. package/dist/components/p-DfphSI9S.js.map +1 -0
  87. package/dist/components/p-DfwvHxYi.js +2 -0
  88. package/dist/components/p-DfwvHxYi.js.map +1 -0
  89. package/dist/components/{p-BQE2Uj4p.js → p-DfzXyVoM.js} +2 -2
  90. package/dist/components/{p-BQE2Uj4p.js.map → p-DfzXyVoM.js.map} +1 -1
  91. package/dist/components/{p-z_8aZHHo.js → p-DixX2_Bd.js} +2 -2
  92. package/dist/components/{p-CipTqNIr.js → p-Djkk338n.js} +2 -2
  93. package/dist/components/{p-Dx7wJaLv.js → p-DvMCOJwc.js} +2 -2
  94. package/dist/components/{p-ChsBscqZ.js → p-DwFnt0W0.js} +2 -2
  95. package/dist/components/{p-uAtciSyr.js → p-OFJpWJcN.js} +2 -2
  96. package/dist/components/{p-Bx_72ylJ.js → p-S5QCVAdH.js} +2 -2
  97. package/dist/docs/components.json +13 -13
  98. package/dist/lib/utils/index.js +3 -1
  99. package/dist/lib/utils/index.js.map +2 -2
  100. package/dist/lib/utils/resolve-css-length-px.d.ts +1 -1
  101. package/dist/mcp-data/registry/card-overview.json +1 -1
  102. package/dist/mcp-data/registry/chart.json +3 -3
  103. package/dist/mcp-data/registry/code-block.json +3 -2
  104. package/dist/mcp-data/registry/mobile-bar-nav.json +3 -3
  105. package/dist/mcp-data/registry/mobile-header.json +3 -3
  106. package/dist/mcp-data/registry/mobile-section-switcher.json +3 -2
  107. package/dist/mcp-data/registry/mobile-sheet-nav.json +6 -6
  108. package/dist/mcp-data/registry/modal.json +1 -1
  109. package/dist/mcp-data/registry/panel-tools.json +1 -1
  110. package/dist/mcp-data/registry/registry.json +12 -10
  111. package/dist/mcp-data/registry/shell-app.json +3 -3
  112. package/dist/mcp-data/registry/shell-tools.json +4 -4
  113. package/dist/mcp-data/registry/table.json +2 -2
  114. package/dist/mcp-data/registry/toast.json +1 -1
  115. package/dist/react/ds-shell-app.d.ts +2 -5
  116. package/dist/react/ds-shell-tools.d.ts +2 -5
  117. package/dist/styles/responsive.css +293 -0
  118. package/dist/types/components/CodeBlock/CodeBlock.d.ts +8 -0
  119. package/dist/types/components/ShellApp/ShellApp.d.ts +2 -5
  120. package/dist/types/components/ShellTools/ShellTools.d.ts +2 -5
  121. package/dist/types/components.d.ts +4 -10
  122. package/dist/types/utils/resolve-css-length-px.d.ts +1 -1
  123. package/package.json +2 -1
  124. package/dist/components/p-BE9ladwu.js +0 -2
  125. package/dist/components/p-BE9ladwu.js.map +0 -1
  126. package/dist/components/p-BfdYb8L4.js +0 -2
  127. package/dist/components/p-BfdYb8L4.js.map +0 -1
  128. package/dist/components/p-BuwYiPCW.js +0 -2
  129. package/dist/components/p-BuwYiPCW.js.map +0 -1
  130. package/dist/components/p-cktJHlYv.js +0 -2
  131. package/dist/components/p-cktJHlYv.js.map +0 -1
  132. package/dist/components/p-hmvu1lmP.js.map +0 -1
  133. package/dist/components/p-iEhBF5Oi.js +0 -2
  134. package/dist/components/p-iEhBF5Oi.js.map +0 -1
  135. package/dist/components/p-ioGH5SoA.js.map +0 -1
  136. /package/dist/components/{p-PNY7auMh.js.map → p-B5rN3slq.js.map} +0 -0
  137. /package/dist/components/{p-kQ6wvk-r.js.map → p-B9Q7fXxW.js.map} +0 -0
  138. /package/dist/components/{p-BgpzSEWv.js.map → p-BT6xQogY.js.map} +0 -0
  139. /package/dist/components/{p-lJeAJKa5.js.map → p-BqIRExej.js.map} +0 -0
  140. /package/dist/components/{p-BtSEQ-FW.js.map → p-C8D853vs.js.map} +0 -0
  141. /package/dist/components/{p-CHYYRdRJ.js.map → p-CEB8RFih.js.map} +0 -0
  142. /package/dist/components/{p-DuCCuJKI.js.map → p-CGKjjJZm.js.map} +0 -0
  143. /package/dist/components/{p-2ZV8JA88.js.map → p-CMrHRjjH.js.map} +0 -0
  144. /package/dist/components/{p-DwuqJhR5.js.map → p-CPNA8raE.js.map} +0 -0
  145. /package/dist/components/{p-BrB05_X8.js.map → p-CaNMVLhm.js.map} +0 -0
  146. /package/dist/components/{p-BjLGayCj.js.map → p-D-cZ6cRI.js.map} +0 -0
  147. /package/dist/components/{p-DQeUT9o2.js.map → p-D4r6oWyQ.js.map} +0 -0
  148. /package/dist/components/{p-DoRgzvup.js.map → p-DGDF4NMV.js.map} +0 -0
  149. /package/dist/components/{p-DSpKpoSG.js.map → p-DKG26pp5.js.map} +0 -0
  150. /package/dist/components/{p-z_8aZHHo.js.map → p-DixX2_Bd.js.map} +0 -0
  151. /package/dist/components/{p-CipTqNIr.js.map → p-Djkk338n.js.map} +0 -0
  152. /package/dist/components/{p-Dx7wJaLv.js.map → p-DvMCOJwc.js.map} +0 -0
  153. /package/dist/components/{p-ChsBscqZ.js.map → p-DwFnt0W0.js.map} +0 -0
  154. /package/dist/components/{p-uAtciSyr.js.map → p-OFJpWJcN.js.map} +0 -0
  155. /package/dist/components/{p-Bx_72ylJ.js.map → p-S5QCVAdH.js.map} +0 -0
@@ -44,6 +44,7 @@
44
44
  ],
45
45
  "responsiveBehavior": [
46
46
  "Use only in the dedicated mobile header presentation.",
47
+ "Triggers, sheet rows, and labels use the medium baseline so the global responsive profile applies mobile enlargement once.",
47
48
  "While a section sheet is open or closing, an opaque bottom-edge surface matches the shade composited over the primary mobile bar background, allowing browsers that sample fixed edges to tint their controls independently of the top sheet. Browser tint remains best-effort and requires native-device verification."
48
49
  ],
49
50
  "references": [
@@ -246,12 +247,12 @@
246
247
  "files": [
247
248
  {
248
249
  "path": "src/wc/components/MobileSectionSwitcher/MobileSectionSwitcher.css",
249
- "content": "@import '../../utils/focus-ring.css';\n@import '../../utils/interaction-fill.css';\n@import '../../utils/radius.css';\n@import '../../utils/control-density.css';\n\n:host {\n display: block;\n min-width: 0;\n max-width: 100%;\n color: var(--color-foreground-primary);\n}\n\n.mobile-section-switcher {\n appearance: none;\n display: grid;\n grid-template-columns: var(--dimension-size-100) minmax(0, auto) var(--dimension-size-100);\n align-items: center;\n justify-content: center;\n gap: var(--dimension-space-050);\n max-width: 100%;\n min-height: var(--dimension-size-400);\n padding-inline: var(--dimension-space-100);\n border: 0;\n border-radius: var(--dimension-radius-100);\n background: var(--color-background-secondary);\n color: inherit;\n box-sizing: border-box;\n cursor: default;\n}\n\n.mobile-section-switcher__label {\n min-width: 0;\n max-width: 100%;\n text-align: center;\n}\n\n.mobile-section-switcher__position {\n width: var(--dimension-size-050);\n height: var(--dimension-size-050);\n border-radius: var(--dimension-radius-half);\n background: currentColor;\n opacity: 0;\n}\n\n.mobile-section-switcher__position--visible {\n opacity: 1;\n}\n\n.mobile-section-switcher--sheet {\n grid-template-columns: minmax(0, auto);\n min-height: var(--dimension-size-500);\n padding-inline: var(--dimension-space-150);\n border-radius: var(--dimension-radius-half);\n background: transparent;\n --ds-interaction-border-width: var(--dimension-stroke-width-012);\n --ds-interaction-border-color: var(--color-border-tertiary);\n}\n\n.mobile-section-switcher--combined {\n display: flex;\n}\n\n.mobile-section-switcher__page-label {\n flex: 0 1 auto;\n min-width: 0;\n}\n\n.mobile-section-switcher__separator {\n flex: 0 0 auto;\n}\n\n.mobile-section-switcher--combined .mobile-section-switcher__label {\n flex: 0 0 auto;\n max-width: 50%;\n}\n\n.mobile-section-sheet {\n position: fixed;\n inset: 0 0 auto;\n width: 100%;\n max-width: none;\n max-height: calc(100dvh - var(--dimension-size-500) - var(--dimension-space-200));\n margin: 0;\n padding: 0;\n border: 0;\n border-radius: 0;\n box-sizing: border-box;\n overflow: visible;\n background: transparent;\n color: var(--color-foreground-secondary);\n outline: none;\n}\n\n.mobile-section-sheet[open] {\n display: flex;\n flex-direction: column;\n}\n\n.mobile-section-sheet[open] .mobile-section-sheet__items {\n animation: mobileSectionSheetIn var(--effect-motion-short-2) both;\n}\n\n.mobile-section-sheet::backdrop {\n background: var(--color-background-shade);\n animation: mobileSectionShadeIn var(--effect-motion-short-2) both;\n}\n\n.mobile-section-sheet--closing[open] .mobile-section-sheet__items {\n animation-name: mobileSectionSheetOut;\n}\n\n.mobile-section-sheet--closing::backdrop {\n animation-name: mobileSectionShadeOut;\n}\n\n.mobile-section-sheet__items {\n display: flex;\n flex-direction: column;\n gap: var(--dimension-space-100);\n min-height: 0;\n max-height: inherit;\n box-sizing: border-box;\n background: var(--color-background-primary);\n box-shadow: var(--effect-elevation-elevated-floating);\n padding: var(--dimension-space-100);\n padding-block-start: max(var(--dimension-space-100), env(safe-area-inset-top, 0px));\n overflow-y: auto;\n overscroll-behavior: contain;\n}\n\n.mobile-section-sheet__item {\n appearance: none;\n flex: 0 0 auto;\n display: flex;\n align-items: center;\n height: var(--ds-control-height);\n width: 100%;\n min-width: 0;\n padding: 0 var(--ds-control-padding-inline);\n box-sizing: border-box;\n border: 0;\n border-radius: var(--ds-control-radius);\n background: transparent;\n color: inherit;\n text-align: start;\n}\n\n.mobile-section-sheet__item[aria-current='page'] {\n color: var(--color-foreground-primary);\n}\n\n.mobile-section-sheet__item:disabled {\n color: var(--color-foreground-tertiary);\n}\n\n.mobile-section-sheet__label {\n min-width: 0;\n padding-inline: var(--ds-control-label-inset);\n}\n\n.mobile-section-sheet__divider {\n flex: 0 0 auto;\n height: var(--dimension-stroke-width-012);\n margin-inline: var(--dimension-space-100);\n background: var(--color-border-tertiary);\n}\n\n@keyframes mobileSectionSheetIn {\n from {\n transform: translateY(-100%);\n }\n to {\n transform: translateY(0);\n }\n}\n\n@keyframes mobileSectionSheetOut {\n from {\n transform: translateY(0);\n }\n to {\n transform: translateY(-100%);\n }\n}\n\n@keyframes mobileSectionShadeIn {\n from {\n opacity: 0;\n }\n to {\n opacity: 1;\n }\n}\n\n@keyframes mobileSectionShadeOut {\n from {\n opacity: 1;\n }\n to {\n opacity: 0;\n }\n}\n\n/* Safari can preserve :focus-visible across modal entry and focus return after\n * a tap. Track the actual input, while leaving focus and keyboard access intact. */\n:host(.mobile-section-switcher--pointer-focus) .ds-focus-ring-inset::after {\n outline: none;\n}\n\n/* Keep this opaque strip in the modal top layer, outside the moving sheet.\n * Safari samples fixed edge backgrounds to tint its browser controls. */\n.mobile-section-sheet__browser-edge {\n position: fixed;\n inset: auto 0 0;\n height: max(var(--dimension-space-200), env(safe-area-inset-bottom, 0px));\n pointer-events: none;\n color: var(--color-background-primary);\n background-color: var(--_mobile-section-edge-color, var(--color-background-primary));\n}\n\n.mobile-section-sheet[open] .mobile-section-sheet__browser-edge {\n animation: mobileSectionEdgeIn var(--effect-motion-short-2) both;\n}\n\n.mobile-section-sheet--closing[open] .mobile-section-sheet__browser-edge {\n animation-name: mobileSectionEdgeOut;\n}\n\n/* Animate the opaque color, not opacity: the backdrop is already fading below. */\n@keyframes mobileSectionEdgeIn {\n from {\n background-color: var(--color-background-primary);\n }\n to {\n background-color: var(--_mobile-section-edge-color, var(--color-background-primary));\n }\n}\n\n@keyframes mobileSectionEdgeOut {\n from {\n background-color: var(--_mobile-section-edge-color, var(--color-background-primary));\n }\n to {\n background-color: var(--color-background-primary);\n }\n}\n\n@media (prefers-reduced-motion: reduce) {\n .mobile-section-sheet[open] .mobile-section-sheet__items,\n .mobile-section-sheet[open] .mobile-section-sheet__browser-edge,\n .mobile-section-sheet::backdrop {\n animation: none;\n }\n}\n",
250
+ "content": "@import '../../utils/focus-ring.css';\n@import '../../utils/interaction-fill.css';\n@import '../../utils/radius.css';\n@import '../../utils/control-density.css';\n\n:host {\n display: block;\n min-width: 0;\n max-width: 100%;\n color: var(--color-foreground-primary);\n}\n\n.mobile-section-switcher {\n appearance: none;\n display: grid;\n grid-template-columns: var(--dimension-size-100) minmax(0, auto) var(--dimension-size-100);\n align-items: center;\n justify-content: center;\n gap: var(--dimension-space-050);\n max-width: 100%;\n min-height: var(--dimension-size-400);\n padding-inline: var(--dimension-space-100);\n border: 0;\n border-radius: var(--dimension-radius-100);\n background: var(--color-background-secondary);\n color: inherit;\n box-sizing: border-box;\n cursor: default;\n}\n\n.mobile-section-switcher__label {\n min-width: 0;\n max-width: 100%;\n text-align: center;\n}\n\n.mobile-section-switcher__position {\n width: var(--dimension-size-050);\n height: var(--dimension-size-050);\n border-radius: var(--dimension-radius-half);\n background: currentColor;\n opacity: 0;\n}\n\n.mobile-section-switcher__position--visible {\n opacity: 1;\n}\n\n.mobile-section-switcher--sheet {\n grid-template-columns: minmax(0, auto);\n min-height: var(--dimension-size-400);\n padding-inline: var(--dimension-space-100);\n border-radius: var(--dimension-radius-half);\n background: transparent;\n --ds-interaction-border-width: var(--dimension-stroke-width-012);\n --ds-interaction-border-color: var(--color-border-tertiary);\n}\n\n.mobile-section-switcher--combined {\n display: flex;\n}\n\n.mobile-section-switcher__page-label {\n flex: 0 1 auto;\n min-width: 0;\n}\n\n.mobile-section-switcher__separator {\n flex: 0 0 auto;\n}\n\n.mobile-section-switcher--combined .mobile-section-switcher__label {\n flex: 0 0 auto;\n max-width: 50%;\n}\n\n.mobile-section-sheet {\n position: fixed;\n inset: 0 0 auto;\n width: 100%;\n max-width: none;\n max-height: calc(100dvh - var(--dimension-size-400) - var(--dimension-space-200));\n margin: 0;\n padding: 0;\n border: 0;\n border-radius: 0;\n box-sizing: border-box;\n overflow: visible;\n background: transparent;\n color: var(--color-foreground-secondary);\n outline: none;\n}\n\n.mobile-section-sheet[open] {\n display: flex;\n flex-direction: column;\n}\n\n.mobile-section-sheet[open] .mobile-section-sheet__items {\n animation: mobileSectionSheetIn var(--effect-motion-short-2) both;\n}\n\n.mobile-section-sheet::backdrop {\n background: var(--color-background-shade);\n animation: mobileSectionShadeIn var(--effect-motion-short-2) both;\n}\n\n.mobile-section-sheet--closing[open] .mobile-section-sheet__items {\n animation-name: mobileSectionSheetOut;\n}\n\n.mobile-section-sheet--closing::backdrop {\n animation-name: mobileSectionShadeOut;\n}\n\n.mobile-section-sheet__items {\n display: flex;\n flex-direction: column;\n gap: var(--dimension-space-100);\n min-height: 0;\n max-height: inherit;\n box-sizing: border-box;\n background: var(--color-background-primary);\n box-shadow: var(--effect-elevation-elevated-floating);\n padding: var(--dimension-space-100);\n padding-block-start: max(var(--dimension-space-100), env(safe-area-inset-top, 0px));\n overflow-y: auto;\n overscroll-behavior: contain;\n}\n\n.mobile-section-sheet__item {\n appearance: none;\n flex: 0 0 auto;\n display: flex;\n align-items: center;\n height: var(--ds-control-height);\n width: 100%;\n min-width: 0;\n padding: 0 var(--ds-control-padding-inline);\n box-sizing: border-box;\n border: 0;\n border-radius: var(--ds-control-radius);\n background: transparent;\n color: inherit;\n text-align: start;\n}\n\n.mobile-section-sheet__item[aria-current='page'] {\n color: var(--color-foreground-primary);\n}\n\n.mobile-section-sheet__item:disabled {\n color: var(--color-foreground-tertiary);\n}\n\n.mobile-section-sheet__label {\n min-width: 0;\n padding-inline: var(--ds-control-label-inset);\n}\n\n.mobile-section-sheet__divider {\n flex: 0 0 auto;\n height: var(--dimension-stroke-width-012);\n margin-inline: var(--dimension-space-100);\n background: var(--color-border-tertiary);\n}\n\n@keyframes mobileSectionSheetIn {\n from {\n transform: translateY(-100%);\n }\n to {\n transform: translateY(0);\n }\n}\n\n@keyframes mobileSectionSheetOut {\n from {\n transform: translateY(0);\n }\n to {\n transform: translateY(-100%);\n }\n}\n\n@keyframes mobileSectionShadeIn {\n from {\n opacity: 0;\n }\n to {\n opacity: 1;\n }\n}\n\n@keyframes mobileSectionShadeOut {\n from {\n opacity: 1;\n }\n to {\n opacity: 0;\n }\n}\n\n/* Safari can preserve :focus-visible across modal entry and focus return after\n * a tap. Track the actual input, while leaving focus and keyboard access intact. */\n:host(.mobile-section-switcher--pointer-focus) .ds-focus-ring-inset::after {\n outline: none;\n}\n\n/* Keep this opaque strip in the modal top layer, outside the moving sheet.\n * Safari samples fixed edge backgrounds to tint its browser controls. */\n.mobile-section-sheet__browser-edge {\n position: fixed;\n inset: auto 0 0;\n height: max(var(--dimension-space-200), env(safe-area-inset-bottom, 0px));\n pointer-events: none;\n color: var(--color-background-primary);\n background-color: var(--_mobile-section-edge-color, var(--color-background-primary));\n}\n\n.mobile-section-sheet[open] .mobile-section-sheet__browser-edge {\n animation: mobileSectionEdgeIn var(--effect-motion-short-2) both;\n}\n\n.mobile-section-sheet--closing[open] .mobile-section-sheet__browser-edge {\n animation-name: mobileSectionEdgeOut;\n}\n\n/* Animate the opaque color, not opacity: the backdrop is already fading below. */\n@keyframes mobileSectionEdgeIn {\n from {\n background-color: var(--color-background-primary);\n }\n to {\n background-color: var(--_mobile-section-edge-color, var(--color-background-primary));\n }\n}\n\n@keyframes mobileSectionEdgeOut {\n from {\n background-color: var(--_mobile-section-edge-color, var(--color-background-primary));\n }\n to {\n background-color: var(--color-background-primary);\n }\n}\n\n@media (prefers-reduced-motion: reduce) {\n .mobile-section-sheet[open] .mobile-section-sheet__items,\n .mobile-section-sheet[open] .mobile-section-sheet__browser-edge,\n .mobile-section-sheet::backdrop {\n animation: none;\n }\n}\n",
250
251
  "type": "registry:ui"
251
252
  },
252
253
  {
253
254
  "path": "src/wc/components/MobileSectionSwitcher/MobileSectionSwitcher.tsx",
254
- "content": "import {\n Component,\n Element,\n Event,\n EventEmitter,\n h,\n Host,\n Prop,\n State,\n Watch,\n} from '@stencil/core';\nimport type { MenuItemData, MenuSection } from '../Menu/menu-types';\nimport {\n getSelectableTabs,\n isTabDivider,\n type TabItem,\n type TabItemTab,\n} from '../TabGroup/tab-item-utils';\nimport { resolveMobileSectionPosition } from './mobile-section-switcher-utils';\nimport { resolveMotionTimeMs, TOKEN_DEFAULTS } from '../../utils';\nimport { ConnectionTasks } from '../../utils/connection-tasks';\n\nlet nextMobileSectionSwitcherId = 0;\n\n@Component({\n tag: 'ds-mobile-section-switcher',\n styleUrl: 'MobileSectionSwitcher.css',\n scoped: true,\n})\nexport class MobileSectionSwitcher {\n @Element() el!: HTMLElement;\n\n /** Ordered controlled sections. Divider entries group the popup menu. */\n @Prop() sections: TabItem[] = [];\n /** Controlled selected section id. */\n @Prop() value: string = '';\n /** Accessible name for the section chooser. */\n @Prop() navigationLabel: string = 'Change page section';\n /** Anchored local-view menu or viewport-edge sheet for primary page sections. */\n @Prop() presentation: 'menu' | 'sheet' = 'menu';\n /** Stable page identity shown before the selected section in a sheet trigger. */\n @Prop() pageLabel: string = '';\n\n /** Selection intent. The route, tool, or workflow owner updates `value`. */\n @Event() dsChange!: EventEmitter<string>;\n\n @State() private menuOpen = false;\n @State() private menuSurfaceOpen = false;\n @State() private initialFocusVisible = false;\n @State() private pointerFocus = false;\n @State() private sheetClosing = false;\n @State() private focusedSection = '';\n\n private readonly instanceId = nextMobileSectionSwitcherId++;\n private readonly triggerId = `ds-mobile-section-switcher-trigger-${this.instanceId}`;\n private readonly menuId = `ds-mobile-section-switcher-menu-${this.instanceId}`;\n private triggerEl: HTMLButtonElement | null = null;\n private dialogEl: HTMLDialogElement | null = null;\n private browserEdgeEl: HTMLDivElement | null = null;\n private browserEdgeColors = '';\n private themeObserver?: MutationObserver;\n private closeTimer: ReturnType<typeof setTimeout> | null = null;\n private visibilityObserver?: ResizeObserver;\n private loaded = false;\n private boundDocument?: Document;\n private readonly tasks = new ConnectionTasks(() => this.el.isConnected);\n\n private readonly handleDocumentKeyDown = (event: KeyboardEvent) => {\n if (['Shift', 'Control', 'Alt', 'Meta'].includes(event.key)) return;\n this.pointerFocus = false;\n };\n\n componentDidLoad() {\n this.loaded = true;\n this.connectResources();\n }\n\n connectedCallback() {\n if (this.loaded) this.connectResources();\n }\n\n private connectResources() {\n if (!this.el.isConnected || this.boundDocument) return;\n this.boundDocument = this.el.ownerDocument;\n this.boundDocument.addEventListener('keydown', this.handleDocumentKeyDown, true);\n this.visibilityObserver = new ResizeObserver(\n this.tasks.guard(() => {\n if (this.dialogEl?.open && !this.triggerEl?.getClientRects().length) {\n this.finishSheetClose(false);\n }\n })\n );\n this.visibilityObserver.observe(this.el);\n }\n\n componentDidRender() {\n if (!this.el.isConnected || !this.dialogEl) return;\n if (this.presentation === 'sheet' && this.menuOpen && !this.dialogEl.open) {\n const initialSection = this.focusedSection;\n this.updateBrowserEdgeColor();\n this.observeBrowserEdgeTheme();\n this.dialogEl.showModal();\n this.focusSheetItem(initialSection);\n }\n if (this.sheetClosing && this.dialogEl.open && !this.closeTimer) {\n const duration = resolveMotionTimeMs(\n TOKEN_DEFAULTS.motionShort2,\n TOKEN_DEFAULTS.animationDurationShort3\n );\n if (!duration) this.finishSheetClose();\n else this.closeTimer = setTimeout(() => this.finishSheetClose(), duration);\n }\n }\n\n disconnectedCallback() {\n this.tasks.cancel();\n this.boundDocument?.removeEventListener('keydown', this.handleDocumentKeyDown, true);\n this.boundDocument = undefined;\n this.visibilityObserver?.disconnect();\n this.visibilityObserver = undefined;\n this.pointerFocus = false;\n this.menuSurfaceOpen = false;\n this.finishSheetClose(false);\n }\n\n @Watch('presentation')\n handlePresentationChange() {\n this.menuSurfaceOpen = false;\n this.closeMenu();\n }\n\n @Watch('value')\n @Watch('pageLabel')\n handleContextChange() {\n this.closeMenu();\n }\n\n private finishSheetClose(restoreFocus = true) {\n if (this.closeTimer) clearTimeout(this.closeTimer);\n this.closeTimer = null;\n const wasOpen = this.dialogEl?.open;\n this.dialogEl?.close();\n this.themeObserver?.disconnect();\n this.menuOpen = false;\n this.sheetClosing = false;\n if (wasOpen && restoreFocus && this.triggerEl?.getClientRects().length) this.triggerEl.focus();\n }\n\n private updateBrowserEdgeColor = () => {\n if (!this.el.isConnected || !this.browserEdgeEl || !this.dialogEl) return;\n const base = getComputedStyle(this.browserEdgeEl).color;\n const shade = getComputedStyle(this.dialogEl, '::backdrop').backgroundColor;\n const colors = `${base}|${shade}`;\n if (colors === this.browserEdgeColors) return;\n // Let the browser resolve token colors, including alpha and non-sRGB syntax,\n // then flatten the shade over the bar into the opaque color Safari samples.\n const canvas = this.el.ownerDocument.createElement('canvas');\n canvas.width = canvas.height = 1;\n const context = canvas.getContext('2d');\n if (!context) return;\n context.fillStyle = base;\n context.fillRect(0, 0, 1, 1);\n context.fillStyle = shade;\n context.fillRect(0, 0, 1, 1);\n const [red, green, blue] = context.getImageData(0, 0, 1, 1).data;\n this.browserEdgeEl.style.setProperty(\n '--_mobile-section-edge-color',\n `rgb(${red}, ${green}, ${blue})`\n );\n this.browserEdgeColors = colors;\n };\n\n private observeBrowserEdgeTheme() {\n this.themeObserver ??= new MutationObserver(this.updateBrowserEdgeColor);\n // Token themes are inherited from the page or a containing theme scope.\n for (let ancestor: HTMLElement | null = this.el; ancestor; ancestor = ancestor.parentElement) {\n this.themeObserver.observe(ancestor, {\n attributes: true,\n attributeFilter: ['class', 'style', 'data-theme'],\n });\n }\n }\n\n private focusSheetItem(id: string) {\n const buttons = Array.from(this.dialogEl?.querySelectorAll<HTMLButtonElement>('button') ?? []);\n const button =\n buttons.find(item => item.value === id && !item.disabled) ??\n buttons.find(item => !item.disabled);\n if (button) {\n this.focusedSection = button.value;\n button.focus();\n } else this.dialogEl?.focus();\n }\n\n private handleSheetKeyDown = (event: KeyboardEvent) => {\n if (event.key === 'Escape') {\n event.preventDefault();\n event.stopPropagation();\n this.closeMenu();\n return;\n }\n const sections = this.selectableSections.filter(section => !section.isInactive);\n if (!sections.length) return;\n const index = Math.max(\n 0,\n sections.findIndex(section => section.id === this.focusedSection)\n );\n let next: number;\n if (event.key === 'ArrowDown') next = (index + 1) % sections.length;\n else if (event.key === 'ArrowUp') next = (index + sections.length - 1) % sections.length;\n else if (event.key === 'Home') next = 0;\n else if (event.key === 'End') next = sections.length - 1;\n else if (event.key === 'Tab') next = index;\n else if (\n event.key.length === 1 &&\n !event.ctrlKey &&\n !event.metaKey &&\n !event.altKey &&\n event.key !== ' '\n ) {\n const match = sections.findIndex((_, offset) =>\n sections[(index + offset + 1) % sections.length].label\n .toLowerCase()\n .startsWith(event.key.toLowerCase())\n );\n if (match < 0) return;\n next = (index + match + 1) % sections.length;\n } else return;\n event.preventDefault();\n this.initialFocusVisible = true;\n this.focusSheetItem(sections[next].id);\n };\n\n private get resolvedSections(): TabItem[] {\n return this.sections ?? [];\n }\n\n private get selectableSections(): TabItemTab[] {\n return getSelectableTabs(this.resolvedSections);\n }\n\n private get selectedIndex(): number {\n return resolveMobileSectionPosition(this.resolvedSections, this.value).selectedIndex;\n }\n\n private get selectedSection(): TabItemTab | undefined {\n return this.selectableSections[this.selectedIndex];\n }\n\n private get hasPrevious(): boolean {\n return resolveMobileSectionPosition(this.resolvedSections, this.value).hasPrevious;\n }\n\n private get hasNext(): boolean {\n return resolveMobileSectionPosition(this.resolvedSections, this.value).hasNext;\n }\n\n private get triggerAriaLabel(): string {\n return `${this.presentation === 'sheet' && this.pageLabel ? `${this.pageLabel}. ` : ''}${this.navigationLabel}. Current section: ${this.selectedSection?.label ?? ''}`;\n }\n\n private get menuSections(): MenuSection[] {\n const groups: MenuSection[] = [];\n let items: MenuItemData[] = [];\n const commit = () => {\n if (items.length) groups.push({ items });\n items = [];\n };\n\n for (const section of this.resolvedSections) {\n if (isTabDivider(section)) {\n commit();\n continue;\n }\n items.push({\n label: section.label,\n value: section.id,\n dot: section.dot,\n isSelected: section.id === this.selectedSection?.id,\n isInactive: section.isInactive,\n });\n }\n commit();\n return groups;\n }\n\n @Watch('sections')\n handleSectionsChange() {\n if (this.selectableSections.length <= 1) {\n this.closeMenu();\n this.menuSurfaceOpen = false;\n } else if (this.dialogEl?.open && !this.sheetClosing) {\n this.tasks.frame(() => {\n if (this.dialogEl?.open && !this.sheetClosing) this.focusSheetItem(this.focusedSection);\n });\n }\n }\n\n private toggleMenu = (event: MouseEvent) => {\n if (this.selectableSections.length <= 1) return;\n this.initialFocusVisible = event.detail === 0;\n if (this.presentation === 'sheet') {\n if (this.menuOpen) this.closeMenu();\n else {\n this.focusedSection = this.selectedSection?.id ?? '';\n this.menuOpen = true;\n }\n return;\n }\n this.menuOpen = !this.menuOpen;\n if (this.menuOpen) this.menuSurfaceOpen = true;\n };\n\n private closeMenu = () => {\n this.menuOpen = false;\n if (this.dialogEl?.open) this.sheetClosing = true;\n };\n\n private renderSheet() {\n return (\n <dialog\n id={this.menuId}\n ref={element => {\n this.dialogEl = element ?? null;\n }}\n class={{ 'mobile-section-sheet': true, 'mobile-section-sheet--closing': this.sheetClosing }}\n aria-label={this.navigationLabel}\n tabIndex={-1}\n onCancel={event => {\n event.preventDefault();\n this.closeMenu();\n }}\n onKeyDown={this.handleSheetKeyDown}\n onClick={event => {\n if (!this.dialogEl || event.target !== this.dialogEl) return;\n const rect = this.dialogEl.getBoundingClientRect();\n if (\n event.clientY < rect.top ||\n event.clientY > rect.bottom ||\n event.clientX < rect.left ||\n event.clientX > rect.right\n )\n this.closeMenu();\n }}\n >\n <div class=\"mobile-section-sheet__items\" role=\"menu\" aria-label={this.navigationLabel}>\n {this.resolvedSections.map(section =>\n isTabDivider(section) ? (\n <div class=\"mobile-section-sheet__divider\" role=\"separator\" />\n ) : (\n <button\n type=\"button\"\n role=\"menuitem\"\n value={section.id}\n disabled={section.isInactive}\n aria-current={section.id === this.selectedSection?.id ? 'page' : undefined}\n tabIndex={section.id === this.focusedSection ? 0 : -1}\n class={{\n 'mobile-section-sheet__item': true,\n 'ds-control--lg': true,\n 'ds-interaction-fill': true,\n 'ds-focus-ring-inset': true,\n 'ds-focus-ring--visible':\n this.initialFocusVisible && section.id === this.focusedSection,\n }}\n onFocus={() => {\n this.focusedSection = section.id;\n }}\n onClick={() => {\n if (this.sheetClosing || section.isInactive) return;\n this.closeMenu();\n if (section.id !== this.selectedSection?.id) this.dsChange.emit(section.id);\n }}\n >\n <ds-text\n class=\"mobile-section-sheet__label ds-interaction-fill__content\"\n as=\"span\"\n variant=\"text-body-large\"\n color=\"inherit\"\n emphasis={section.id === this.selectedSection?.id}\n lineTruncation={1}\n >\n {section.label}\n </ds-text>\n </button>\n )\n )}\n </div>\n <div\n ref={element => {\n if (this.browserEdgeEl !== element) this.browserEdgeColors = '';\n this.browserEdgeEl = element ?? null;\n }}\n class=\"mobile-section-sheet__browser-edge\"\n aria-hidden=\"true\"\n />\n </dialog>\n );\n }\n\n private handleSelect = (event: CustomEvent<MenuItemData>) => {\n const id = String(event.detail.value ?? '');\n const section = this.selectableSections.find(candidate => candidate.id === id);\n if (!section || section.isInactive) return;\n this.closeMenu();\n if (id !== this.selectedSection?.id) this.dsChange.emit(id);\n this.tasks.frame(() => this.triggerEl?.focus());\n };\n\n render() {\n const selected = this.selectedSection;\n if (!selected) return <Host />;\n const combined = this.presentation === 'sheet' && !!this.pageLabel;\n\n return (\n <Host\n class={{ 'mobile-section-switcher--pointer-focus': this.pointerFocus }}\n onPointerDown={() => {\n this.pointerFocus = true;\n this.initialFocusVisible = false;\n }}\n >\n <button\n ref={element => {\n this.triggerEl = element ?? null;\n }}\n id={this.triggerId}\n class={{\n 'mobile-section-switcher': true,\n 'mobile-section-switcher--expanded': this.menuOpen,\n 'ds-interaction-fill--surface-open':\n this.menuSurfaceOpen || this.menuOpen || this.sheetClosing,\n 'mobile-section-switcher--sheet': this.presentation === 'sheet',\n 'mobile-section-switcher--combined': combined,\n 'ds-focus-ring-inset': true,\n 'ds-interaction-fill': true,\n }}\n type=\"button\"\n aria-haspopup={this.presentation === 'sheet' ? 'dialog' : 'menu'}\n aria-controls={this.menuId}\n aria-expanded={String(this.menuOpen)}\n aria-label={this.triggerAriaLabel}\n onClick={this.toggleMenu}\n >\n {this.presentation === 'menu' ? (\n <span\n class={{\n 'mobile-section-switcher__position': true,\n 'mobile-section-switcher__position--visible': this.hasPrevious,\n 'ds-interaction-fill__content': true,\n }}\n aria-hidden=\"true\"\n />\n ) : null}\n {combined\n ? [\n <ds-text\n class=\"mobile-section-switcher__page-label ds-interaction-fill__content\"\n as=\"span\"\n variant=\"text-body-large\"\n emphasis\n color=\"inherit\"\n lineTruncation={1}\n >\n {this.pageLabel}\n </ds-text>,\n <ds-text\n class=\"mobile-section-switcher__separator ds-interaction-fill__content\"\n as=\"span\"\n variant=\"text-body-large\"\n color=\"inherit\"\n aria-hidden=\"true\"\n >\n ·\n </ds-text>,\n ]\n : null}\n <ds-text\n class=\"mobile-section-switcher__label ds-interaction-fill__content\"\n as=\"span\"\n variant={this.presentation === 'sheet' ? 'text-body-large' : 'text-body-medium'}\n emphasis\n color=\"inherit\"\n lineTruncation={1}\n >\n {selected.label}\n </ds-text>\n {this.presentation === 'menu' ? (\n <span\n class={{\n 'mobile-section-switcher__position': true,\n 'mobile-section-switcher__position--visible': this.hasNext,\n 'ds-interaction-fill__content': true,\n }}\n aria-hidden=\"true\"\n />\n ) : null}\n </button>\n\n {this.presentation === 'sheet' || this.sheetClosing ? (\n this.renderSheet()\n ) : this.selectableSections.length > 1 ? (\n <ds-menu\n id={this.menuId}\n anchorId={this.triggerId}\n align=\"center\"\n menuLabel={this.navigationLabel}\n open={this.menuOpen}\n initialFocusVisible={this.initialFocusVisible}\n sections={this.menuSections}\n onDsClose={this.closeMenu}\n onDsAfterClose={() => {\n if (!this.menuOpen) this.menuSurfaceOpen = false;\n }}\n onDsSelect={this.handleSelect}\n />\n ) : null}\n </Host>\n );\n }\n}\n",
255
+ "content": "import {\n Component,\n Element,\n Event,\n EventEmitter,\n h,\n Host,\n Prop,\n State,\n Watch,\n} from '@stencil/core';\nimport type { MenuItemData, MenuSection } from '../Menu/menu-types';\nimport {\n getSelectableTabs,\n isTabDivider,\n type TabItem,\n type TabItemTab,\n} from '../TabGroup/tab-item-utils';\nimport { resolveMobileSectionPosition } from './mobile-section-switcher-utils';\nimport { resolveMotionTimeMs, TOKEN_DEFAULTS } from '../../utils';\nimport { ConnectionTasks } from '../../utils/connection-tasks';\n\nlet nextMobileSectionSwitcherId = 0;\n\n@Component({\n tag: 'ds-mobile-section-switcher',\n styleUrl: 'MobileSectionSwitcher.css',\n scoped: true,\n})\nexport class MobileSectionSwitcher {\n @Element() el!: HTMLElement;\n\n /** Ordered controlled sections. Divider entries group the popup menu. */\n @Prop() sections: TabItem[] = [];\n /** Controlled selected section id. */\n @Prop() value: string = '';\n /** Accessible name for the section chooser. */\n @Prop() navigationLabel: string = 'Change page section';\n /** Anchored local-view menu or viewport-edge sheet for primary page sections. */\n @Prop() presentation: 'menu' | 'sheet' = 'menu';\n /** Stable page identity shown before the selected section in a sheet trigger. */\n @Prop() pageLabel: string = '';\n\n /** Selection intent. The route, tool, or workflow owner updates `value`. */\n @Event() dsChange!: EventEmitter<string>;\n\n @State() private menuOpen = false;\n @State() private menuSurfaceOpen = false;\n @State() private initialFocusVisible = false;\n @State() private pointerFocus = false;\n @State() private sheetClosing = false;\n @State() private focusedSection = '';\n\n private readonly instanceId = nextMobileSectionSwitcherId++;\n private readonly triggerId = `ds-mobile-section-switcher-trigger-${this.instanceId}`;\n private readonly menuId = `ds-mobile-section-switcher-menu-${this.instanceId}`;\n private triggerEl: HTMLButtonElement | null = null;\n private dialogEl: HTMLDialogElement | null = null;\n private browserEdgeEl: HTMLDivElement | null = null;\n private browserEdgeColors = '';\n private themeObserver?: MutationObserver;\n private closeTimer: ReturnType<typeof setTimeout> | null = null;\n private visibilityObserver?: ResizeObserver;\n private loaded = false;\n private boundDocument?: Document;\n private readonly tasks = new ConnectionTasks(() => this.el.isConnected);\n\n private readonly handleDocumentKeyDown = (event: KeyboardEvent) => {\n if (['Shift', 'Control', 'Alt', 'Meta'].includes(event.key)) return;\n this.pointerFocus = false;\n };\n\n componentDidLoad() {\n this.loaded = true;\n this.connectResources();\n }\n\n connectedCallback() {\n if (this.loaded) this.connectResources();\n }\n\n private connectResources() {\n if (!this.el.isConnected || this.boundDocument) return;\n this.boundDocument = this.el.ownerDocument;\n this.boundDocument.addEventListener('keydown', this.handleDocumentKeyDown, true);\n this.visibilityObserver = new ResizeObserver(\n this.tasks.guard(() => {\n if (this.dialogEl?.open && !this.triggerEl?.getClientRects().length) {\n this.finishSheetClose(false);\n }\n })\n );\n this.visibilityObserver.observe(this.el);\n }\n\n componentDidRender() {\n if (!this.el.isConnected || !this.dialogEl) return;\n if (this.presentation === 'sheet' && this.menuOpen && !this.dialogEl.open) {\n const initialSection = this.focusedSection;\n this.updateBrowserEdgeColor();\n this.observeBrowserEdgeTheme();\n this.dialogEl.showModal();\n this.focusSheetItem(initialSection);\n }\n if (this.sheetClosing && this.dialogEl.open && !this.closeTimer) {\n const duration = resolveMotionTimeMs(\n TOKEN_DEFAULTS.motionShort2,\n TOKEN_DEFAULTS.animationDurationShort3\n );\n if (!duration) this.finishSheetClose();\n else this.closeTimer = setTimeout(() => this.finishSheetClose(), duration);\n }\n }\n\n disconnectedCallback() {\n this.tasks.cancel();\n this.boundDocument?.removeEventListener('keydown', this.handleDocumentKeyDown, true);\n this.boundDocument = undefined;\n this.visibilityObserver?.disconnect();\n this.visibilityObserver = undefined;\n this.pointerFocus = false;\n this.menuSurfaceOpen = false;\n this.finishSheetClose(false);\n }\n\n @Watch('presentation')\n handlePresentationChange() {\n this.menuSurfaceOpen = false;\n this.closeMenu();\n }\n\n @Watch('value')\n @Watch('pageLabel')\n handleContextChange() {\n this.closeMenu();\n }\n\n private finishSheetClose(restoreFocus = true) {\n if (this.closeTimer) clearTimeout(this.closeTimer);\n this.closeTimer = null;\n const wasOpen = this.dialogEl?.open;\n this.dialogEl?.close();\n this.themeObserver?.disconnect();\n this.menuOpen = false;\n this.sheetClosing = false;\n if (wasOpen && restoreFocus && this.triggerEl?.getClientRects().length) this.triggerEl.focus();\n }\n\n private updateBrowserEdgeColor = () => {\n if (!this.el.isConnected || !this.browserEdgeEl || !this.dialogEl) return;\n const base = getComputedStyle(this.browserEdgeEl).color;\n const shade = getComputedStyle(this.dialogEl, '::backdrop').backgroundColor;\n const colors = `${base}|${shade}`;\n if (colors === this.browserEdgeColors) return;\n // Let the browser resolve token colors, including alpha and non-sRGB syntax,\n // then flatten the shade over the bar into the opaque color Safari samples.\n const canvas = this.el.ownerDocument.createElement('canvas');\n canvas.width = canvas.height = 1;\n const context = canvas.getContext('2d');\n if (!context) return;\n context.fillStyle = base;\n context.fillRect(0, 0, 1, 1);\n context.fillStyle = shade;\n context.fillRect(0, 0, 1, 1);\n const [red, green, blue] = context.getImageData(0, 0, 1, 1).data;\n this.browserEdgeEl.style.setProperty(\n '--_mobile-section-edge-color',\n `rgb(${red}, ${green}, ${blue})`\n );\n this.browserEdgeColors = colors;\n };\n\n private observeBrowserEdgeTheme() {\n this.themeObserver ??= new MutationObserver(this.updateBrowserEdgeColor);\n // Token themes are inherited from the page or a containing theme scope.\n for (let ancestor: HTMLElement | null = this.el; ancestor; ancestor = ancestor.parentElement) {\n this.themeObserver.observe(ancestor, {\n attributes: true,\n attributeFilter: ['class', 'style', 'data-theme'],\n });\n }\n }\n\n private focusSheetItem(id: string) {\n const buttons = Array.from(this.dialogEl?.querySelectorAll<HTMLButtonElement>('button') ?? []);\n const button =\n buttons.find(item => item.value === id && !item.disabled) ??\n buttons.find(item => !item.disabled);\n if (button) {\n this.focusedSection = button.value;\n button.focus();\n } else this.dialogEl?.focus();\n }\n\n private handleSheetKeyDown = (event: KeyboardEvent) => {\n if (event.key === 'Escape') {\n event.preventDefault();\n event.stopPropagation();\n this.closeMenu();\n return;\n }\n const sections = this.selectableSections.filter(section => !section.isInactive);\n if (!sections.length) return;\n const index = Math.max(\n 0,\n sections.findIndex(section => section.id === this.focusedSection)\n );\n let next: number;\n if (event.key === 'ArrowDown') next = (index + 1) % sections.length;\n else if (event.key === 'ArrowUp') next = (index + sections.length - 1) % sections.length;\n else if (event.key === 'Home') next = 0;\n else if (event.key === 'End') next = sections.length - 1;\n else if (event.key === 'Tab') next = index;\n else if (\n event.key.length === 1 &&\n !event.ctrlKey &&\n !event.metaKey &&\n !event.altKey &&\n event.key !== ' '\n ) {\n const match = sections.findIndex((_, offset) =>\n sections[(index + offset + 1) % sections.length].label\n .toLowerCase()\n .startsWith(event.key.toLowerCase())\n );\n if (match < 0) return;\n next = (index + match + 1) % sections.length;\n } else return;\n event.preventDefault();\n this.initialFocusVisible = true;\n this.focusSheetItem(sections[next].id);\n };\n\n private get resolvedSections(): TabItem[] {\n return this.sections ?? [];\n }\n\n private get selectableSections(): TabItemTab[] {\n return getSelectableTabs(this.resolvedSections);\n }\n\n private get selectedIndex(): number {\n return resolveMobileSectionPosition(this.resolvedSections, this.value).selectedIndex;\n }\n\n private get selectedSection(): TabItemTab | undefined {\n return this.selectableSections[this.selectedIndex];\n }\n\n private get hasPrevious(): boolean {\n return resolveMobileSectionPosition(this.resolvedSections, this.value).hasPrevious;\n }\n\n private get hasNext(): boolean {\n return resolveMobileSectionPosition(this.resolvedSections, this.value).hasNext;\n }\n\n private get triggerAriaLabel(): string {\n return `${this.presentation === 'sheet' && this.pageLabel ? `${this.pageLabel}. ` : ''}${this.navigationLabel}. Current section: ${this.selectedSection?.label ?? ''}`;\n }\n\n private get menuSections(): MenuSection[] {\n const groups: MenuSection[] = [];\n let items: MenuItemData[] = [];\n const commit = () => {\n if (items.length) groups.push({ items });\n items = [];\n };\n\n for (const section of this.resolvedSections) {\n if (isTabDivider(section)) {\n commit();\n continue;\n }\n items.push({\n label: section.label,\n value: section.id,\n dot: section.dot,\n isSelected: section.id === this.selectedSection?.id,\n isInactive: section.isInactive,\n });\n }\n commit();\n return groups;\n }\n\n @Watch('sections')\n handleSectionsChange() {\n if (this.selectableSections.length <= 1) {\n this.closeMenu();\n this.menuSurfaceOpen = false;\n } else if (this.dialogEl?.open && !this.sheetClosing) {\n this.tasks.frame(() => {\n if (this.dialogEl?.open && !this.sheetClosing) this.focusSheetItem(this.focusedSection);\n });\n }\n }\n\n private toggleMenu = (event: MouseEvent) => {\n if (this.selectableSections.length <= 1) return;\n this.initialFocusVisible = event.detail === 0;\n if (this.presentation === 'sheet') {\n if (this.menuOpen) this.closeMenu();\n else {\n this.focusedSection = this.selectedSection?.id ?? '';\n this.menuOpen = true;\n }\n return;\n }\n this.menuOpen = !this.menuOpen;\n if (this.menuOpen) this.menuSurfaceOpen = true;\n };\n\n private closeMenu = () => {\n this.menuOpen = false;\n if (this.dialogEl?.open) this.sheetClosing = true;\n };\n\n private renderSheet() {\n return (\n <dialog\n id={this.menuId}\n ref={element => {\n this.dialogEl = element ?? null;\n }}\n class={{ 'mobile-section-sheet': true, 'mobile-section-sheet--closing': this.sheetClosing }}\n aria-label={this.navigationLabel}\n tabIndex={-1}\n onCancel={event => {\n event.preventDefault();\n this.closeMenu();\n }}\n onKeyDown={this.handleSheetKeyDown}\n onClick={event => {\n if (!this.dialogEl || event.target !== this.dialogEl) return;\n const rect = this.dialogEl.getBoundingClientRect();\n if (\n event.clientY < rect.top ||\n event.clientY > rect.bottom ||\n event.clientX < rect.left ||\n event.clientX > rect.right\n )\n this.closeMenu();\n }}\n >\n <div class=\"mobile-section-sheet__items\" role=\"menu\" aria-label={this.navigationLabel}>\n {this.resolvedSections.map(section =>\n isTabDivider(section) ? (\n <div class=\"mobile-section-sheet__divider\" role=\"separator\" />\n ) : (\n <button\n type=\"button\"\n role=\"menuitem\"\n value={section.id}\n disabled={section.isInactive}\n aria-current={section.id === this.selectedSection?.id ? 'page' : undefined}\n tabIndex={section.id === this.focusedSection ? 0 : -1}\n class={{\n 'mobile-section-sheet__item': true,\n 'ds-control--md': true,\n 'ds-interaction-fill': true,\n 'ds-focus-ring-inset': true,\n 'ds-focus-ring--visible':\n this.initialFocusVisible && section.id === this.focusedSection,\n }}\n onFocus={() => {\n this.focusedSection = section.id;\n }}\n onClick={() => {\n if (this.sheetClosing || section.isInactive) return;\n this.closeMenu();\n if (section.id !== this.selectedSection?.id) this.dsChange.emit(section.id);\n }}\n >\n <ds-text\n class=\"mobile-section-sheet__label ds-interaction-fill__content\"\n as=\"span\"\n variant=\"text-body-medium\"\n color=\"inherit\"\n emphasis={section.id === this.selectedSection?.id}\n lineTruncation={1}\n >\n {section.label}\n </ds-text>\n </button>\n )\n )}\n </div>\n <div\n ref={element => {\n if (this.browserEdgeEl !== element) this.browserEdgeColors = '';\n this.browserEdgeEl = element ?? null;\n }}\n class=\"mobile-section-sheet__browser-edge\"\n aria-hidden=\"true\"\n />\n </dialog>\n );\n }\n\n private handleSelect = (event: CustomEvent<MenuItemData>) => {\n const id = String(event.detail.value ?? '');\n const section = this.selectableSections.find(candidate => candidate.id === id);\n if (!section || section.isInactive) return;\n this.closeMenu();\n if (id !== this.selectedSection?.id) this.dsChange.emit(id);\n this.tasks.frame(() => this.triggerEl?.focus());\n };\n\n render() {\n const selected = this.selectedSection;\n if (!selected) return <Host />;\n const combined = this.presentation === 'sheet' && !!this.pageLabel;\n\n return (\n <Host\n class={{ 'mobile-section-switcher--pointer-focus': this.pointerFocus }}\n onPointerDown={() => {\n this.pointerFocus = true;\n this.initialFocusVisible = false;\n }}\n >\n <button\n ref={element => {\n this.triggerEl = element ?? null;\n }}\n id={this.triggerId}\n class={{\n 'mobile-section-switcher': true,\n 'mobile-section-switcher--expanded': this.menuOpen,\n 'ds-interaction-fill--surface-open':\n this.menuSurfaceOpen || this.menuOpen || this.sheetClosing,\n 'mobile-section-switcher--sheet': this.presentation === 'sheet',\n 'mobile-section-switcher--combined': combined,\n 'ds-focus-ring-inset': true,\n 'ds-interaction-fill': true,\n }}\n type=\"button\"\n aria-haspopup={this.presentation === 'sheet' ? 'dialog' : 'menu'}\n aria-controls={this.menuId}\n aria-expanded={String(this.menuOpen)}\n aria-label={this.triggerAriaLabel}\n onClick={this.toggleMenu}\n >\n {this.presentation === 'menu' ? (\n <span\n class={{\n 'mobile-section-switcher__position': true,\n 'mobile-section-switcher__position--visible': this.hasPrevious,\n 'ds-interaction-fill__content': true,\n }}\n aria-hidden=\"true\"\n />\n ) : null}\n {combined\n ? [\n <ds-text\n class=\"mobile-section-switcher__page-label ds-interaction-fill__content\"\n as=\"span\"\n variant=\"text-body-medium\"\n emphasis\n color=\"inherit\"\n lineTruncation={1}\n >\n {this.pageLabel}\n </ds-text>,\n <ds-text\n class=\"mobile-section-switcher__separator ds-interaction-fill__content\"\n as=\"span\"\n variant=\"text-body-medium\"\n color=\"inherit\"\n aria-hidden=\"true\"\n >\n ·\n </ds-text>,\n ]\n : null}\n <ds-text\n class=\"mobile-section-switcher__label ds-interaction-fill__content\"\n as=\"span\"\n variant=\"text-body-medium\"\n emphasis\n color=\"inherit\"\n lineTruncation={1}\n >\n {selected.label}\n </ds-text>\n {this.presentation === 'menu' ? (\n <span\n class={{\n 'mobile-section-switcher__position': true,\n 'mobile-section-switcher__position--visible': this.hasNext,\n 'ds-interaction-fill__content': true,\n }}\n aria-hidden=\"true\"\n />\n ) : null}\n </button>\n\n {this.presentation === 'sheet' || this.sheetClosing ? (\n this.renderSheet()\n ) : this.selectableSections.length > 1 ? (\n <ds-menu\n id={this.menuId}\n anchorId={this.triggerId}\n align=\"center\"\n menuLabel={this.navigationLabel}\n open={this.menuOpen}\n initialFocusVisible={this.initialFocusVisible}\n sections={this.menuSections}\n onDsClose={this.closeMenu}\n onDsAfterClose={() => {\n if (!this.menuOpen) this.menuSurfaceOpen = false;\n }}\n onDsSelect={this.handleSelect}\n />\n ) : null}\n </Host>\n );\n }\n}\n",
255
256
  "type": "registry:ui"
256
257
  },
257
258
  {
@@ -2,7 +2,7 @@
2
2
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3
3
  "name": "mobile-sheet-nav",
4
4
  "title": "MobileSheetNav",
5
- "description": "Full-height mobile navigation sheet with a single brand, context, and utility header above large primary destinations.",
5
+ "description": "Full-height mobile navigation sheet with a single brand, context, and utility header above primary destinations.",
6
6
  "type": "registry:ui",
7
7
  "meta": {
8
8
  "library": "@ds-mo/ui",
@@ -13,7 +13,7 @@
13
13
  "intent": {
14
14
  "audience": "specialized",
15
15
  "status": "experimental",
16
- "summary": "Full-height mobile navigation sheet with a single brand, context, and utility header above large primary destinations.",
16
+ "summary": "Full-height mobile navigation sheet with a single brand, context, and utility header above primary destinations.",
17
17
  "useWhen": [
18
18
  "The mobile ShellApp Menu control needs to expose the complete primary navigation collection."
19
19
  ],
@@ -44,9 +44,9 @@
44
44
  ],
45
45
  "responsiveBehavior": [
46
46
  "The pane fills the mobile content stage while the bottom bar remains available.",
47
- "The sheet header and scrollable navigation body each use the shared 8px chrome inset; the context TabGroup and primary controls share a 40px height and 24px icon scale.",
47
+ "The sheet header and scrollable navigation body use shared chrome spacing; the context TabGroup, primary controls, icons, and text share the medium baseline and receive mobile enlargement from the global responsive profile.",
48
48
  "When the optional Account shortcut is hidden, the Dashboard and Settings TabGroup fills the centered two-thirds top-bar lane and gives both context options equal width.",
49
- "Dashboard and Settings preserve their supplied visual groups with an 8px gap between destinations inside a section and a 32px gap between sections."
49
+ "Dashboard and Settings preserve their supplied visual groups with token-backed destination and section gaps."
50
50
  ],
51
51
  "references": [
52
52
  {
@@ -413,12 +413,12 @@
413
413
  "files": [
414
414
  {
415
415
  "path": "src/wc/components/MobileSheetNav/MobileSheetNav.css",
416
- "content": "@import '../../utils/radius.css';\n@import '../../utils/control-density.css';\n@import '../../utils/chrome-layout.css';\n@import '../../utils/focus-ring.css';\n@import '../../utils/interaction-fill.css';\n@import '../../utils/nav-disclosure.css';\n\n:host {\n display: block;\n min-width: 0;\n min-height: 0;\n height: 100%;\n background: var(--color-background-primary);\n}\n\n:host(:not([open])) {\n display: none;\n}\n\n.mobile-sheet-nav {\n display: grid;\n grid-template-rows: auto minmax(0, 1fr);\n height: 100%;\n min-height: 0;\n background: var(--color-background-primary);\n}\n\n.mobile-sheet-nav__header {\n position: relative;\n grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);\n}\n\n.mobile-sheet-nav__header--expanded-context {\n grid-template-columns:\n minmax(var(--dimension-size-500), 1fr)\n minmax(0, 4fr)\n minmax(var(--dimension-size-500), 1fr);\n}\n\n.mobile-sheet-nav__header--expanded-context .mobile-sheet-nav__context {\n width: 100%;\n}\n\n.mobile-sheet-nav__header::after {\n content: '';\n position: absolute;\n inset-inline: 0;\n inset-block-end: 0;\n height: var(--dimension-stroke-width-012);\n background: var(--color-border-tertiary);\n pointer-events: none;\n}\n\n.mobile-sheet-nav__brand {\n display: flex;\n align-items: center;\n justify-content: flex-start;\n min-width: 0;\n}\n\n.mobile-sheet-nav__logo {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: var(--dimension-size-500);\n height: var(--dimension-size-500);\n padding: var(--dimension-space-100);\n box-sizing: border-box;\n color: var(--color-foreground-primary);\n}\n\n.mobile-sheet-nav__logo-mark {\n width: var(--dimension-iconography-lg);\n height: var(--dimension-iconography-lg);\n flex: 0 0 auto;\n}\n\n.mobile-sheet-nav__context {\n justify-self: center;\n}\n\n.mobile-sheet-nav__actions {\n display: flex;\n align-items: center;\n justify-content: flex-end;\n gap: var(--dimension-space-050);\n min-width: 0;\n}\n\n.mobile-sheet-nav__body {\n min-height: 0;\n overflow: auto;\n overscroll-behavior: contain;\n}\n\n.mobile-sheet-nav__sections {\n display: grid;\n gap: var(--dimension-space-400);\n}\n\n.mobile-sheet-nav__items {\n display: grid;\n gap: var(--dimension-space-100);\n}\n\n.mobile-sheet-nav__item {\n appearance: none;\n width: 100%;\n height: var(--ds-control-height);\n min-height: var(--ds-control-height);\n padding-inline: var(--ds-control-padding-inline);\n border: 0;\n border-radius: var(--ds-control-radius);\n background: transparent;\n color: var(--color-foreground-secondary);\n display: flex;\n align-items: center;\n justify-content: flex-start;\n gap: var(--ds-control-gap);\n cursor: default;\n text-align: start;\n box-sizing: border-box;\n}\n\n.mobile-sheet-nav__item--selected {\n color: var(--color-foreground-primary);\n}\n\n.mobile-sheet-nav__item-label {\n min-width: 0;\n padding-inline: var(--ds-control-label-inset);\n}\n\n.mobile-sheet-nav__dot {\n margin-inline-start: auto;\n}\n\n.mobile-sheet-nav__children {\n display: flex;\n flex-direction: column;\n gap: var(--dimension-space-100);\n min-height: 0;\n overflow: hidden;\n}\n\n.mobile-sheet-nav__child {\n flex: 0 0 var(--ds-control-height);\n padding-inline-start: calc(\n var(--ds-control-padding-inline) + var(--dimension-iconography-lg) + var(--ds-control-gap)\n );\n}\n\n.mobile-sheet-nav__child:first-child {\n margin-block-start: var(--dimension-space-100);\n}\n\n.mobile-sheet-nav__child:disabled {\n color: var(--color-foreground-tertiary);\n}\n\n.mobile-sheet-nav__parent--muted {\n color: var(--color-foreground-secondary);\n}\n\n.mobile-sheet-nav__divider-line {\n display: block;\n height: var(--dimension-stroke-width-012);\n margin-inline: var(--dimension-space-100);\n background: var(--color-border-tertiary);\n}\n\n.mobile-sheet-nav__divider--before .mobile-sheet-nav__divider-line {\n margin-block: var(--dimension-space-050) var(--dimension-space-150);\n}\n\n.mobile-sheet-nav__divider--after .mobile-sheet-nav__divider-line {\n margin-block: var(--dimension-space-150) var(--dimension-space-050);\n}\n",
416
+ "content": "@import '../../utils/radius.css';\n@import '../../utils/control-density.css';\n@import '../../utils/chrome-layout.css';\n@import '../../utils/focus-ring.css';\n@import '../../utils/interaction-fill.css';\n@import '../../utils/nav-disclosure.css';\n\n:host {\n display: block;\n min-width: 0;\n min-height: 0;\n height: 100%;\n background: var(--color-background-primary);\n}\n\n:host(:not([open])) {\n display: none;\n}\n\n.mobile-sheet-nav {\n display: grid;\n grid-template-rows: auto minmax(0, 1fr);\n height: 100%;\n min-height: 0;\n background: var(--color-background-primary);\n}\n\n.mobile-sheet-nav__header {\n position: relative;\n grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);\n}\n\n.mobile-sheet-nav__header--expanded-context {\n grid-template-columns:\n minmax(var(--dimension-size-400), 1fr)\n minmax(0, 4fr)\n minmax(var(--dimension-size-400), 1fr);\n}\n\n.mobile-sheet-nav__header--expanded-context .mobile-sheet-nav__context {\n width: 100%;\n}\n\n.mobile-sheet-nav__header::after {\n content: '';\n position: absolute;\n inset-inline: 0;\n inset-block-end: 0;\n height: var(--dimension-stroke-width-012);\n background: var(--color-border-tertiary);\n pointer-events: none;\n}\n\n.mobile-sheet-nav__brand {\n display: flex;\n align-items: center;\n justify-content: flex-start;\n min-width: 0;\n}\n\n.mobile-sheet-nav__logo {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: var(--dimension-size-400);\n height: var(--dimension-size-400);\n padding: var(--dimension-space-075);\n box-sizing: border-box;\n color: var(--color-foreground-primary);\n}\n\n.mobile-sheet-nav__logo-mark {\n width: var(--dimension-iconography-md);\n height: var(--dimension-iconography-md);\n flex: 0 0 auto;\n}\n\n.mobile-sheet-nav__context {\n justify-self: center;\n}\n\n.mobile-sheet-nav__actions {\n display: flex;\n align-items: center;\n justify-content: flex-end;\n gap: var(--dimension-space-050);\n min-width: 0;\n}\n\n.mobile-sheet-nav__body {\n min-height: 0;\n overflow: auto;\n overscroll-behavior: contain;\n}\n\n.mobile-sheet-nav__sections {\n display: grid;\n gap: var(--dimension-space-200);\n}\n\n.mobile-sheet-nav__items {\n display: grid;\n gap: var(--dimension-space-050);\n}\n\n.mobile-sheet-nav__item {\n appearance: none;\n width: 100%;\n height: var(--ds-control-height);\n min-height: var(--ds-control-height);\n padding-inline: var(--ds-control-padding-inline);\n border: 0;\n border-radius: var(--ds-control-radius);\n background: transparent;\n color: var(--color-foreground-secondary);\n display: flex;\n align-items: center;\n justify-content: flex-start;\n gap: var(--ds-control-gap);\n cursor: default;\n text-align: start;\n box-sizing: border-box;\n}\n\n.mobile-sheet-nav__item--selected {\n color: var(--color-foreground-primary);\n}\n\n.mobile-sheet-nav__item-label {\n min-width: 0;\n padding-inline: var(--ds-control-label-inset);\n}\n\n.mobile-sheet-nav__dot {\n margin-inline-start: auto;\n}\n\n.mobile-sheet-nav__children {\n display: flex;\n flex-direction: column;\n gap: var(--dimension-space-050);\n min-height: 0;\n overflow: hidden;\n}\n\n.mobile-sheet-nav__child {\n flex: 0 0 var(--ds-control-height);\n padding-inline-start: calc(\n var(--ds-control-padding-inline) + var(--dimension-iconography-md) + var(--ds-control-gap)\n );\n}\n\n.mobile-sheet-nav__child:first-child {\n margin-block-start: var(--dimension-space-050);\n}\n\n.mobile-sheet-nav__child:disabled {\n color: var(--color-foreground-tertiary);\n}\n\n.mobile-sheet-nav__parent--muted {\n color: var(--color-foreground-secondary);\n}\n\n.mobile-sheet-nav__divider-line {\n display: block;\n height: var(--dimension-stroke-width-012);\n margin-inline: var(--dimension-space-075);\n background: var(--color-border-tertiary);\n}\n\n.mobile-sheet-nav__divider--before .mobile-sheet-nav__divider-line {\n margin-block: var(--dimension-space-050) var(--dimension-space-100);\n}\n\n.mobile-sheet-nav__divider--after .mobile-sheet-nav__divider-line {\n margin-block: var(--dimension-space-100) var(--dimension-space-050);\n}\n",
417
417
  "type": "registry:ui"
418
418
  },
419
419
  {
420
420
  "path": "src/wc/components/MobileSheetNav/MobileSheetNav.tsx",
421
- "content": "import {\n Component,\n Element,\n Event,\n EventEmitter,\n h,\n Host,\n Listen,\n Prop,\n State,\n Watch,\n} from '@stencil/core';\nimport type { NavChromeStyle } from '../../shell/nav-chrome';\nimport { derivePanelNavSelectionFromUrl } from '../PanelNav/panel-nav-utils';\nimport type {\n PanelNavChildItem,\n PanelNavChildSelectDetail,\n PanelNavGroup,\n PanelNavItem,\n PanelNavPresentation,\n} from '../PanelNav/panel-nav-types';\n\ntype MobileSheetNavHeaderDestinationId = 'account' | 'help';\n\nconst CONTEXT_TABS = [\n { id: 'dashboard', label: 'Dashboard', icon: 'Dashboard', variant: 'icon' as const },\n { id: 'settings', label: 'Settings', icon: 'Gear', variant: 'icon' as const },\n];\nlet nextSheetId = 0;\n\n@Component({\n tag: 'ds-mobile-sheet-nav',\n styleUrl: 'MobileSheetNav.css',\n scoped: true,\n})\nexport class MobileSheetNav {\n @Element() el!: HTMLElement;\n\n @Prop({ reflect: true }) open: boolean = false;\n @Prop() browseContext: NavChromeStyle = 'dashboard';\n @Prop() dashboardGroups: PanelNavGroup[] = [];\n @Prop() settingsGroups: PanelNavGroup[] = [];\n @Prop() currentUrl: string = '';\n /** Flat area navigation or inline disclosure of each area's child routes. */\n @Prop() presentation: PanelNavPresentation = 'flat';\n /** Whether the router-derived current area owns the active mobile stage. */\n @Prop() routeSelectionActive: boolean = true;\n @Prop() navigationLabel: string = 'Application navigation';\n @Prop() dashboardLabel: string = 'Dashboard';\n @Prop() settingsLabel: string = 'Settings';\n @Prop() accountLabel: string = 'Account';\n @Prop() helpLabel: string = 'Help & Support';\n /** Show the optional Account shortcut in the sheet header. */\n @Prop() showAccount: boolean = true;\n\n @Event() dsAreaSelect!: EventEmitter<string>;\n /** Selecting a child requests routing; expanding its parent never navigates. */\n @Event() dsNavChildSelect!: EventEmitter<PanelNavChildSelectDetail>;\n @Event() dsBrowseContextChange!: EventEmitter<NavChromeStyle>;\n @Event() dsClose!: EventEmitter<void>;\n @State() private expandedParentId = '';\n private readonly instanceId = nextSheetId++;\n\n private get groups(): PanelNavGroup[] {\n return this.browseContext === 'settings' ? this.settingsGroups : this.dashboardGroups;\n }\n\n private get selection() {\n return derivePanelNavSelectionFromUrl(\n this.routeSelectionActive ? this.currentUrl : '',\n this.groups.flatMap(group => group.items)\n );\n }\n\n componentWillLoad() {\n this.syncExpandedParent();\n }\n\n @Watch('presentation')\n @Watch('currentUrl')\n @Watch('browseContext')\n @Watch('routeSelectionActive')\n syncExpandedParent() {\n this.expandedParentId = this.presentation === 'nested' ? this.selection.parentId : '';\n }\n\n @Watch('open')\n handleOpenChange(open: boolean) {\n if (!open) return;\n this.syncExpandedParent();\n requestAnimationFrame(() => {\n const selected = this.el.querySelector<HTMLElement>('[aria-current=\"page\"]');\n const first = this.el.querySelector<HTMLElement>('.mobile-sheet-nav__item:not(:disabled)');\n (selected ?? first)?.focus({ preventScroll: true });\n });\n }\n\n @Listen('keydown')\n handleKeyDown(event: KeyboardEvent) {\n if (!this.open || event.key !== 'Escape') return;\n event.preventDefault();\n this.dsClose.emit();\n }\n\n private handleContextChange = (event: CustomEvent<string>) => {\n const next = event.detail === 'settings' ? 'settings' : 'dashboard';\n if (next === this.browseContext) return;\n this.dsBrowseContextChange.emit(next);\n };\n\n private renderItem(item: PanelNavItem) {\n const selected = item.id === this.selection.parentId;\n const disclosure = this.presentation === 'nested' && !!item.children?.length;\n const expanded = disclosure && item.id === this.expandedParentId;\n return (\n <button\n id={this.parentId(item.id)}\n type=\"button\"\n class={{\n 'mobile-sheet-nav__item': true,\n 'mobile-sheet-nav__item--selected': selected,\n 'mobile-sheet-nav__parent--muted':\n this.presentation === 'nested' &&\n this.expandedParentId !== '' &&\n this.expandedParentId !== item.id,\n 'ds-control--lg': true,\n 'ds-focus-ring-inset': true,\n 'ds-interaction-fill': true,\n }}\n aria-current={selected && !disclosure ? 'page' : undefined}\n aria-expanded={disclosure ? String(expanded) : undefined}\n aria-controls={disclosure ? this.childrenId(item.id) : undefined}\n onClick={() =>\n disclosure\n ? (this.expandedParentId = expanded ? '' : item.id)\n : this.dsAreaSelect.emit(item.id)\n }\n >\n <ds-icon class=\"ds-interaction-fill__content\" name={item.icon} size=\"lg\" color=\"inherit\" />\n <ds-text\n class=\"mobile-sheet-nav__item-label ds-interaction-fill__content\"\n as=\"span\"\n variant=\"text-body-large\"\n emphasis={selected}\n color=\"inherit\"\n lineTruncation={1}\n >\n {item.label}\n </ds-text>\n {item.dot && (\n <ds-badge\n class=\"mobile-sheet-nav__dot ds-interaction-fill__content\"\n variant=\"dot\"\n hasRing={false}\n label=\"\"\n aria-hidden=\"true\"\n />\n )}\n </button>\n );\n }\n\n private parentId(id: string) {\n return `mobile-sheet-${this.instanceId}-${encodeURIComponent(id)}`;\n }\n private childrenId(id: string) {\n return `${this.parentId(id)}-children`;\n }\n\n private renderChild(parent: PanelNavItem, child: PanelNavChildItem, index: number) {\n const selected = parent.id === this.selection.parentId && child.id === this.selection.childId;\n return (\n <button\n type=\"button\"\n disabled={child.isInactive}\n class={{\n 'mobile-sheet-nav__item': true,\n 'mobile-sheet-nav__child': true,\n 'mobile-sheet-nav__item--selected': selected,\n 'ds-nav-disclosure__item': true,\n 'ds-control--lg': true,\n 'ds-focus-ring-inset': true,\n 'ds-interaction-fill': true,\n }}\n style={{\n '--ds-nav-disclosure-index': String(index),\n '--ds-nav-disclosure-reverse-index': String((parent.children?.length ?? 0) - index - 1),\n }}\n aria-current={selected ? 'page' : undefined}\n onClick={() => {\n if (!child.isInactive)\n this.dsNavChildSelect.emit({\n parentId: parent.id,\n childId: child.id,\n href: child.href,\n });\n }}\n onKeyDown={(event: KeyboardEvent) => {\n if (event.key === 'ArrowLeft') {\n event.preventDefault();\n this.expandedParentId = '';\n this.el.querySelector<HTMLElement>(`#${CSS.escape(this.parentId(parent.id))}`)?.focus();\n }\n }}\n >\n <ds-text\n class=\"mobile-sheet-nav__item-label ds-interaction-fill__content\"\n as=\"span\"\n variant=\"text-body-large\"\n emphasis={selected}\n color=\"inherit\"\n lineTruncation={1}\n >\n {child.label}\n </ds-text>\n {child.dot ? (\n <ds-badge\n class=\"mobile-sheet-nav__dot ds-interaction-fill__content\"\n variant=\"dot\"\n hasRing={false}\n label=\"\"\n aria-hidden=\"true\"\n />\n ) : null}\n </button>\n );\n }\n\n private renderBranchDivider(position: 'before' | 'after', expanded: boolean) {\n return (\n <div\n class={{\n 'ds-nav-disclosure-divider': true,\n 'ds-nav-disclosure-divider--open': expanded,\n 'mobile-sheet-nav__divider': true,\n [`mobile-sheet-nav__divider--${position}`]: true,\n }}\n aria-hidden=\"true\"\n >\n <div class=\"ds-nav-disclosure-divider__clip\">\n <span class=\"mobile-sheet-nav__divider-line\" />\n </div>\n </div>\n );\n }\n\n private renderBranch(item: PanelNavItem, index: number, count: number) {\n const hasChildren = this.presentation === 'nested' && !!item.children?.length;\n const expanded = hasChildren && this.expandedParentId === item.id;\n return (\n <div key={item.id} class=\"mobile-sheet-nav__branch\">\n {hasChildren && index > 0 ? this.renderBranchDivider('before', expanded) : null}\n {this.renderItem(item)}\n {this.presentation === 'nested' && item.children?.length ? (\n <div\n class={{ 'ds-nav-disclosure': true, 'ds-nav-disclosure--open': expanded }}\n inert={!expanded ? true : undefined}\n aria-hidden={!expanded ? 'true' : undefined}\n >\n <div\n class=\"mobile-sheet-nav__children\"\n id={this.childrenId(item.id)}\n role=\"group\"\n aria-labelledby={this.parentId(item.id)}\n >\n {item.children.map((child, index) => this.renderChild(item, child, index))}\n </div>\n </div>\n ) : null}\n {hasChildren && index < count - 1 ? this.renderBranchDivider('after', expanded) : null}\n </div>\n );\n }\n\n private renderLogo() {\n return (\n <span class=\"mobile-sheet-nav__logo\" aria-hidden=\"true\">\n {/* eslint-disable-next-line compomo/prefer-ds-icon -- Motive brand mark is not part of the IcoMo runtime catalog. */}\n <svg\n class=\"mobile-sheet-nav__logo-mark\"\n viewBox=\"0 0 20 20\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n focusable=\"false\"\n aria-hidden=\"true\"\n >\n <path\n d=\"M11.1159 4.31537H7.67021L2.53401 13.0703H0V15.6875H4.02716L8.24319 8.49978V15.6846H11.6342L15.8289 8.47829V15.6846H18.7122V4.3125H15.2559L11.1159 11.3648V4.31537Z\"\n fill=\"currentColor\"\n />\n </svg>\n </span>\n );\n }\n\n private renderHeaderDestination(\n id: MobileSheetNavHeaderDestinationId,\n icon: string,\n label: string\n ) {\n return (\n <ds-tooltip label={label} side=\"bottom\" size=\"sm\">\n <ds-button-unfilled\n variant=\"icon\"\n size=\"lg\"\n icon={icon}\n aria-label={label}\n activeFill={false}\n hasBorder={false}\n onDsClick={() => this.dsAreaSelect.emit(id)}\n />\n </ds-tooltip>\n );\n }\n\n render() {\n const contextTabs = [\n { ...CONTEXT_TABS[0], label: this.dashboardLabel },\n { ...CONTEXT_TABS[1], label: this.settingsLabel },\n ];\n\n return (\n <Host aria-hidden={this.open ? undefined : 'true'} inert={this.open ? undefined : true}>\n <section id=\"ds-mobile-sheet-nav\" class=\"mobile-sheet-nav\">\n <header\n class={{\n 'mobile-sheet-nav__header': true,\n 'mobile-sheet-nav__header--expanded-context': !this.showAccount,\n 'ds-chrome-grid': true,\n 'ds-chrome-space--md': true,\n }}\n >\n <div class=\"mobile-sheet-nav__brand\">{this.renderLogo()}</div>\n <ds-tab-group\n class=\"mobile-sheet-nav__context\"\n tabs={contextTabs}\n value={this.browseContext}\n size=\"lg\"\n width={this.showAccount ? 'hug' : 'fill'}\n aria-label=\"Browse context\"\n onDsChange={this.handleContextChange}\n />\n <div class=\"mobile-sheet-nav__actions\">\n {this.renderHeaderDestination('help', 'QuestionCircle', this.helpLabel)}\n {this.showAccount\n ? this.renderHeaderDestination('account', 'PersonCircle', this.accountLabel)\n : null}\n </div>\n </header>\n\n <nav\n class=\"mobile-sheet-nav__body ds-chrome-column ds-chrome-space--md\"\n aria-label={this.navigationLabel}\n >\n <div class=\"mobile-sheet-nav__sections\">\n {this.groups\n .filter(group => group.items.length > 0)\n .map(group => (\n <div class=\"mobile-sheet-nav__items\">\n {group.items.map((item, index) =>\n this.renderBranch(item, index, group.items.length)\n )}\n </div>\n ))}\n </div>\n </nav>\n </section>\n </Host>\n );\n }\n}\n",
421
+ "content": "import {\n Component,\n Element,\n Event,\n EventEmitter,\n h,\n Host,\n Listen,\n Prop,\n State,\n Watch,\n} from '@stencil/core';\nimport type { NavChromeStyle } from '../../shell/nav-chrome';\nimport { derivePanelNavSelectionFromUrl } from '../PanelNav/panel-nav-utils';\nimport type {\n PanelNavChildItem,\n PanelNavChildSelectDetail,\n PanelNavGroup,\n PanelNavItem,\n PanelNavPresentation,\n} from '../PanelNav/panel-nav-types';\n\ntype MobileSheetNavHeaderDestinationId = 'account' | 'help';\n\nconst CONTEXT_TABS = [\n { id: 'dashboard', label: 'Dashboard', icon: 'Dashboard', variant: 'icon' as const },\n { id: 'settings', label: 'Settings', icon: 'Gear', variant: 'icon' as const },\n];\nlet nextSheetId = 0;\n\n@Component({\n tag: 'ds-mobile-sheet-nav',\n styleUrl: 'MobileSheetNav.css',\n scoped: true,\n})\nexport class MobileSheetNav {\n @Element() el!: HTMLElement;\n\n @Prop({ reflect: true }) open: boolean = false;\n @Prop() browseContext: NavChromeStyle = 'dashboard';\n @Prop() dashboardGroups: PanelNavGroup[] = [];\n @Prop() settingsGroups: PanelNavGroup[] = [];\n @Prop() currentUrl: string = '';\n /** Flat area navigation or inline disclosure of each area's child routes. */\n @Prop() presentation: PanelNavPresentation = 'flat';\n /** Whether the router-derived current area owns the active mobile stage. */\n @Prop() routeSelectionActive: boolean = true;\n @Prop() navigationLabel: string = 'Application navigation';\n @Prop() dashboardLabel: string = 'Dashboard';\n @Prop() settingsLabel: string = 'Settings';\n @Prop() accountLabel: string = 'Account';\n @Prop() helpLabel: string = 'Help & Support';\n /** Show the optional Account shortcut in the sheet header. */\n @Prop() showAccount: boolean = true;\n\n @Event() dsAreaSelect!: EventEmitter<string>;\n /** Selecting a child requests routing; expanding its parent never navigates. */\n @Event() dsNavChildSelect!: EventEmitter<PanelNavChildSelectDetail>;\n @Event() dsBrowseContextChange!: EventEmitter<NavChromeStyle>;\n @Event() dsClose!: EventEmitter<void>;\n @State() private expandedParentId = '';\n private readonly instanceId = nextSheetId++;\n\n private get groups(): PanelNavGroup[] {\n return this.browseContext === 'settings' ? this.settingsGroups : this.dashboardGroups;\n }\n\n private get selection() {\n return derivePanelNavSelectionFromUrl(\n this.routeSelectionActive ? this.currentUrl : '',\n this.groups.flatMap(group => group.items)\n );\n }\n\n componentWillLoad() {\n this.syncExpandedParent();\n }\n\n @Watch('presentation')\n @Watch('currentUrl')\n @Watch('browseContext')\n @Watch('routeSelectionActive')\n syncExpandedParent() {\n this.expandedParentId = this.presentation === 'nested' ? this.selection.parentId : '';\n }\n\n @Watch('open')\n handleOpenChange(open: boolean) {\n if (!open) return;\n this.syncExpandedParent();\n requestAnimationFrame(() => {\n const selected = this.el.querySelector<HTMLElement>('[aria-current=\"page\"]');\n const first = this.el.querySelector<HTMLElement>('.mobile-sheet-nav__item:not(:disabled)');\n (selected ?? first)?.focus({ preventScroll: true });\n });\n }\n\n @Listen('keydown')\n handleKeyDown(event: KeyboardEvent) {\n if (!this.open || event.key !== 'Escape') return;\n event.preventDefault();\n this.dsClose.emit();\n }\n\n private handleContextChange = (event: CustomEvent<string>) => {\n const next = event.detail === 'settings' ? 'settings' : 'dashboard';\n if (next === this.browseContext) return;\n this.dsBrowseContextChange.emit(next);\n };\n\n private renderItem(item: PanelNavItem) {\n const selected = item.id === this.selection.parentId;\n const disclosure = this.presentation === 'nested' && !!item.children?.length;\n const expanded = disclosure && item.id === this.expandedParentId;\n return (\n <button\n id={this.parentId(item.id)}\n type=\"button\"\n class={{\n 'mobile-sheet-nav__item': true,\n 'mobile-sheet-nav__item--selected': selected,\n 'mobile-sheet-nav__parent--muted':\n this.presentation === 'nested' &&\n this.expandedParentId !== '' &&\n this.expandedParentId !== item.id,\n 'ds-control--md': true,\n 'ds-focus-ring-inset': true,\n 'ds-interaction-fill': true,\n }}\n aria-current={selected && !disclosure ? 'page' : undefined}\n aria-expanded={disclosure ? String(expanded) : undefined}\n aria-controls={disclosure ? this.childrenId(item.id) : undefined}\n onClick={() =>\n disclosure\n ? (this.expandedParentId = expanded ? '' : item.id)\n : this.dsAreaSelect.emit(item.id)\n }\n >\n <ds-icon class=\"ds-interaction-fill__content\" name={item.icon} size=\"md\" color=\"inherit\" />\n <ds-text\n class=\"mobile-sheet-nav__item-label ds-interaction-fill__content\"\n as=\"span\"\n variant=\"text-body-medium\"\n emphasis={selected}\n color=\"inherit\"\n lineTruncation={1}\n >\n {item.label}\n </ds-text>\n {item.dot && (\n <ds-badge\n class=\"mobile-sheet-nav__dot ds-interaction-fill__content\"\n variant=\"dot\"\n hasRing={false}\n label=\"\"\n aria-hidden=\"true\"\n />\n )}\n </button>\n );\n }\n\n private parentId(id: string) {\n return `mobile-sheet-${this.instanceId}-${encodeURIComponent(id)}`;\n }\n private childrenId(id: string) {\n return `${this.parentId(id)}-children`;\n }\n\n private renderChild(parent: PanelNavItem, child: PanelNavChildItem, index: number) {\n const selected = parent.id === this.selection.parentId && child.id === this.selection.childId;\n return (\n <button\n type=\"button\"\n disabled={child.isInactive}\n class={{\n 'mobile-sheet-nav__item': true,\n 'mobile-sheet-nav__child': true,\n 'mobile-sheet-nav__item--selected': selected,\n 'ds-nav-disclosure__item': true,\n 'ds-control--md': true,\n 'ds-focus-ring-inset': true,\n 'ds-interaction-fill': true,\n }}\n style={{\n '--ds-nav-disclosure-index': String(index),\n '--ds-nav-disclosure-reverse-index': String((parent.children?.length ?? 0) - index - 1),\n }}\n aria-current={selected ? 'page' : undefined}\n onClick={() => {\n if (!child.isInactive)\n this.dsNavChildSelect.emit({\n parentId: parent.id,\n childId: child.id,\n href: child.href,\n });\n }}\n onKeyDown={(event: KeyboardEvent) => {\n if (event.key === 'ArrowLeft') {\n event.preventDefault();\n this.expandedParentId = '';\n this.el.querySelector<HTMLElement>(`#${CSS.escape(this.parentId(parent.id))}`)?.focus();\n }\n }}\n >\n <ds-text\n class=\"mobile-sheet-nav__item-label ds-interaction-fill__content\"\n as=\"span\"\n variant=\"text-body-medium\"\n emphasis={selected}\n color=\"inherit\"\n lineTruncation={1}\n >\n {child.label}\n </ds-text>\n {child.dot ? (\n <ds-badge\n class=\"mobile-sheet-nav__dot ds-interaction-fill__content\"\n variant=\"dot\"\n hasRing={false}\n label=\"\"\n aria-hidden=\"true\"\n />\n ) : null}\n </button>\n );\n }\n\n private renderBranchDivider(position: 'before' | 'after', expanded: boolean) {\n return (\n <div\n class={{\n 'ds-nav-disclosure-divider': true,\n 'ds-nav-disclosure-divider--open': expanded,\n 'mobile-sheet-nav__divider': true,\n [`mobile-sheet-nav__divider--${position}`]: true,\n }}\n aria-hidden=\"true\"\n >\n <div class=\"ds-nav-disclosure-divider__clip\">\n <span class=\"mobile-sheet-nav__divider-line\" />\n </div>\n </div>\n );\n }\n\n private renderBranch(item: PanelNavItem, index: number, count: number) {\n const hasChildren = this.presentation === 'nested' && !!item.children?.length;\n const expanded = hasChildren && this.expandedParentId === item.id;\n return (\n <div key={item.id} class=\"mobile-sheet-nav__branch\">\n {hasChildren && index > 0 ? this.renderBranchDivider('before', expanded) : null}\n {this.renderItem(item)}\n {this.presentation === 'nested' && item.children?.length ? (\n <div\n class={{ 'ds-nav-disclosure': true, 'ds-nav-disclosure--open': expanded }}\n inert={!expanded ? true : undefined}\n aria-hidden={!expanded ? 'true' : undefined}\n >\n <div\n class=\"mobile-sheet-nav__children\"\n id={this.childrenId(item.id)}\n role=\"group\"\n aria-labelledby={this.parentId(item.id)}\n >\n {item.children.map((child, index) => this.renderChild(item, child, index))}\n </div>\n </div>\n ) : null}\n {hasChildren && index < count - 1 ? this.renderBranchDivider('after', expanded) : null}\n </div>\n );\n }\n\n private renderLogo() {\n return (\n <span class=\"mobile-sheet-nav__logo\" aria-hidden=\"true\">\n {/* eslint-disable-next-line compomo/prefer-ds-icon -- Motive brand mark is not part of the IcoMo runtime catalog. */}\n <svg\n class=\"mobile-sheet-nav__logo-mark\"\n viewBox=\"0 0 20 20\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n focusable=\"false\"\n aria-hidden=\"true\"\n >\n <path\n d=\"M11.1159 4.31537H7.67021L2.53401 13.0703H0V15.6875H4.02716L8.24319 8.49978V15.6846H11.6342L15.8289 8.47829V15.6846H18.7122V4.3125H15.2559L11.1159 11.3648V4.31537Z\"\n fill=\"currentColor\"\n />\n </svg>\n </span>\n );\n }\n\n private renderHeaderDestination(\n id: MobileSheetNavHeaderDestinationId,\n icon: string,\n label: string\n ) {\n return (\n <ds-tooltip label={label} side=\"bottom\" size=\"sm\">\n <ds-button-unfilled\n variant=\"icon\"\n size=\"md\"\n icon={icon}\n aria-label={label}\n activeFill={false}\n hasBorder={false}\n onDsClick={() => this.dsAreaSelect.emit(id)}\n />\n </ds-tooltip>\n );\n }\n\n render() {\n const contextTabs = [\n { ...CONTEXT_TABS[0], label: this.dashboardLabel },\n { ...CONTEXT_TABS[1], label: this.settingsLabel },\n ];\n\n return (\n <Host aria-hidden={this.open ? undefined : 'true'} inert={this.open ? undefined : true}>\n <section id=\"ds-mobile-sheet-nav\" class=\"mobile-sheet-nav\">\n <header\n class={{\n 'mobile-sheet-nav__header': true,\n 'mobile-sheet-nav__header--expanded-context': !this.showAccount,\n 'ds-chrome-grid': true,\n 'ds-chrome-space--md': true,\n }}\n >\n <div class=\"mobile-sheet-nav__brand\">{this.renderLogo()}</div>\n <ds-tab-group\n class=\"mobile-sheet-nav__context\"\n tabs={contextTabs}\n value={this.browseContext}\n size=\"md\"\n width={this.showAccount ? 'hug' : 'fill'}\n aria-label=\"Browse context\"\n onDsChange={this.handleContextChange}\n />\n <div class=\"mobile-sheet-nav__actions\">\n {this.renderHeaderDestination('help', 'QuestionCircle', this.helpLabel)}\n {this.showAccount\n ? this.renderHeaderDestination('account', 'PersonCircle', this.accountLabel)\n : null}\n </div>\n </header>\n\n <nav\n class=\"mobile-sheet-nav__body ds-chrome-column ds-chrome-space--md\"\n aria-label={this.navigationLabel}\n >\n <div class=\"mobile-sheet-nav__sections\">\n {this.groups\n .filter(group => group.items.length > 0)\n .map(group => (\n <div class=\"mobile-sheet-nav__items\">\n {group.items.map((item, index) =>\n this.renderBranch(item, index, group.items.length)\n )}\n </div>\n ))}\n </div>\n </nav>\n </section>\n </Host>\n );\n }\n}\n",
422
422
  "type": "registry:ui"
423
423
  }
424
424
  ]
@@ -317,7 +317,7 @@
317
317
  "files": [
318
318
  {
319
319
  "path": "src/wc/components/Modal/Modal.css",
320
- "content": "@import '../../utils/radius.css';\n@import '../../utils/chrome-header.css';\n@import '../../utils/chrome-layout.css';\n@import '../../utils/forced-colors.css';\n\n:host {\n display: contents;\n}\n\n.modal-dialog {\n display: none;\n flex-direction: column;\n max-width: none;\n max-height: calc(100vh - 2 * var(--dimension-space-200));\n margin: auto;\n padding: 0;\n overflow: hidden;\n color: var(--color-foreground-primary);\n background-color: var(--color-background-primary);\n border: 0;\n border-radius: var(--ds-radius-modal);\n box-shadow: var(--effect-elevation-elevated-floating);\n outline: none;\n box-sizing: border-box;\n animation: modalDialogIn var(--effect-motion-short-2) forwards;\n}\n\n.modal-dialog[open] {\n display: flex;\n}\n\n.modal-dialog::backdrop {\n background-color: var(--color-background-shade);\n animation: modalBackdropIn var(--effect-motion-short-2) forwards;\n}\n\n.modal-dialog--closing {\n animation: modalDialogOut var(--effect-motion-short-2) forwards;\n}\n\n.modal-dialog--closing::backdrop {\n animation: modalBackdropOut var(--effect-motion-short-2) forwards;\n}\n\n@keyframes modalBackdropIn {\n from {\n opacity: 0;\n }\n to {\n opacity: 1;\n }\n}\n\n@keyframes modalBackdropOut {\n from {\n opacity: 1;\n }\n to {\n opacity: 0;\n }\n}\n\n@keyframes modalDialogIn {\n from {\n opacity: 0;\n transform: scale(0.97) translateY(4px);\n }\n to {\n opacity: 1;\n transform: scale(1) translateY(0);\n }\n}\n\n@keyframes modalDialogOut {\n from {\n opacity: 1;\n transform: scale(1) translateY(0);\n }\n to {\n opacity: 0;\n transform: scale(0.97) translateY(4px);\n }\n}\n\n@media (prefers-reduced-motion: reduce) {\n .modal-dialog[open],\n .modal-dialog--closing {\n animation: none;\n transform: none;\n }\n\n .modal-dialog::backdrop {\n animation: none;\n opacity: 1;\n }\n\n .modal-dialog--closing,\n .modal-dialog--closing::backdrop {\n opacity: 0;\n }\n}\n\n.modal-header {\n align-items: flex-start;\n flex: 0 0 auto;\n color: var(--color-foreground-primary);\n}\n\n.modal-copy,\nds-text.modal-heading,\nds-text.modal-description {\n width: auto;\n text-align: left;\n}\n\nds-text.modal-heading {\n -webkit-user-select: none;\n user-select: none;\n}\n\n.modal-close-tooltip {\n flex: 0 0 auto;\n}\n\n.modal-content {\n min-height: 0;\n padding: var(--dimension-space-200);\n overflow-y: auto;\n flex: 1 1 auto;\n box-sizing: border-box;\n}\n\n.modal-footer {\n justify-content: flex-start;\n height: var(--dimension-size-800);\n flex: 0 0 auto;\n border-top: var(--dimension-stroke-width-012) solid var(--color-border-tertiary);\n}\n\n.modal-footer__actions {\n display: flex;\n align-items: center;\n gap: var(--dimension-space-100);\n min-width: 0;\n}\n\n.modal-footer--empty {\n display: none;\n}\n\n.modal-footer__actions slot {\n display: contents;\n}\n\n@media (forced-colors: active) {\n .modal-dialog {\n box-shadow: none;\n outline: var(--dimension-stroke-width-012) solid var(--ds-forced-color-content);\n outline-offset: calc(-1 * var(--dimension-stroke-width-012));\n }\n}\n",
320
+ "content": "@import '../../utils/radius.css';\n@import '../../utils/chrome-header.css';\n@import '../../utils/chrome-layout.css';\n@import '../../utils/forced-colors.css';\n\n:host {\n display: contents;\n}\n\n.modal-dialog {\n display: none;\n flex-direction: column;\n max-width: none;\n max-height: calc(100vh - 2 * var(--dimension-space-200));\n margin: auto;\n padding: 0;\n overflow: hidden;\n color: var(--color-foreground-primary);\n background-color: var(--color-background-primary);\n border: 0;\n border-radius: var(--ds-radius-modal);\n box-shadow: var(--effect-elevation-elevated-floating);\n outline: none;\n box-sizing: border-box;\n animation: modalDialogIn var(--effect-motion-short-2) forwards;\n}\n\n.modal-dialog[open] {\n display: flex;\n}\n\n.modal-dialog::backdrop {\n background-color: var(--color-background-shade);\n animation: modalBackdropIn var(--effect-motion-short-2) forwards;\n}\n\n.modal-dialog--closing {\n animation: modalDialogOut var(--effect-motion-short-2) forwards;\n}\n\n.modal-dialog--closing::backdrop {\n animation: modalBackdropOut var(--effect-motion-short-2) forwards;\n}\n\n@keyframes modalBackdropIn {\n from {\n opacity: 0;\n }\n to {\n opacity: 1;\n }\n}\n\n@keyframes modalBackdropOut {\n from {\n opacity: 1;\n }\n to {\n opacity: 0;\n }\n}\n\n@keyframes modalDialogIn {\n from {\n opacity: 0;\n transform: scale(0.97) translateY(var(--dimension-space-050));\n }\n to {\n opacity: 1;\n transform: scale(1) translateY(0);\n }\n}\n\n@keyframes modalDialogOut {\n from {\n opacity: 1;\n transform: scale(1) translateY(0);\n }\n to {\n opacity: 0;\n transform: scale(0.97) translateY(var(--dimension-space-050));\n }\n}\n\n@media (prefers-reduced-motion: reduce) {\n .modal-dialog[open],\n .modal-dialog--closing {\n animation: none;\n transform: none;\n }\n\n .modal-dialog::backdrop {\n animation: none;\n opacity: 1;\n }\n\n .modal-dialog--closing,\n .modal-dialog--closing::backdrop {\n opacity: 0;\n }\n}\n\n.modal-header {\n align-items: flex-start;\n flex: 0 0 auto;\n color: var(--color-foreground-primary);\n}\n\n.modal-copy,\nds-text.modal-heading,\nds-text.modal-description {\n width: auto;\n text-align: left;\n}\n\nds-text.modal-heading {\n -webkit-user-select: none;\n user-select: none;\n}\n\n.modal-close-tooltip {\n flex: 0 0 auto;\n}\n\n.modal-content {\n min-height: 0;\n padding: var(--dimension-space-200);\n overflow-y: auto;\n flex: 1 1 auto;\n box-sizing: border-box;\n}\n\n.modal-footer {\n justify-content: flex-start;\n height: var(--dimension-size-800);\n flex: 0 0 auto;\n border-top: var(--dimension-stroke-width-012) solid var(--color-border-tertiary);\n}\n\n.modal-footer__actions {\n display: flex;\n align-items: center;\n gap: var(--dimension-space-100);\n min-width: 0;\n}\n\n.modal-footer--empty {\n display: none;\n}\n\n.modal-footer__actions slot {\n display: contents;\n}\n\n@media (forced-colors: active) {\n .modal-dialog {\n box-shadow: none;\n outline: var(--dimension-stroke-width-012) solid var(--ds-forced-color-content);\n outline-offset: calc(-1 * var(--dimension-stroke-width-012));\n }\n}\n",
321
321
  "type": "registry:ui"
322
322
  },
323
323
  {
@@ -542,7 +542,7 @@
542
542
  "files": [
543
543
  {
544
544
  "path": "src/wc/components/PanelTools/PanelTools.css",
545
- "content": "@import '../../utils/radius.css';\n@import '../../utils/control-density.css';\n@import '../../utils/chrome-layout.css';\n@import '../../utils/focus-ring.css';\n@import '../../utils/interaction-fill.css';\n@import '../../utils/scrollbar-hidden.css';\n\n:host {\n display: block;\n flex: 0 0 auto;\n box-sizing: border-box;\n height: 100%;\n min-height: 0;\n overflow: hidden;\n --_panel-tools-rail-width: var(--dimension-size-600);\n --_panel-tools-drawer-width: var(--ds-shell-panel-tools-width, var(--dimension-panel-width-xs));\n --_panel-tools-border: var(--dimension-stroke-width-012, 1px) solid\n var(--_shell-tools-border, var(--color-border-tertiary));\n --_panel-tools-divider-color: var(--_shell-tools-divider, var(--color-divider-divider));\n --_panel-tools-divider: var(--dimension-stroke-width-012, 1px) solid\n var(--_panel-tools-divider-color);\n --_panel-tools-fg-secondary: var(--_shell-tools-fg-secondary, var(--color-foreground-secondary));\n --_panel-tools-shortcut-fg-secondary: var(\n --_shell-tools-fg-secondary,\n var(--color-chrome-foreground-secondary)\n );\n --_panel-tools-shortcut-border: var(\n --_shell-tools-border-secondary,\n var(--color-chrome-border-secondary)\n );\n --_panel-tools-shortcut-dot-ring: var(\n --_shell-tools-background-theme,\n var(--color-chrome-background-theme)\n );\n /* Match panel-nav expand timing (medium-1); easing differs by direction. */\n --_panel-tools-expand-dur: var(--effect-animation-duration-medium-1, 300ms);\n}\n\n.panel-tools__layout {\n display: flex;\n flex-direction: row-reverse;\n align-items: stretch;\n height: 100%;\n min-height: 0;\n box-sizing: border-box;\n}\n\n/* Clip frame — only max-width animates (slide reveal, same as pre-rail :host). */\n.panel-tools__drawer {\n flex: 0 0 auto;\n max-width: 0;\n min-width: 0;\n height: 100%;\n min-height: 0;\n overflow: hidden;\n box-sizing: border-box;\n transition: none;\n}\n\n:host(.panel-tools--ready) .panel-tools__drawer {\n transition: max-width var(--_panel-tools-expand-dur)\n var(--effect-animation-easing-ease-in-out, ease);\n}\n\n:host(.panel-tools--ready.panel-tools--motion-opening) .panel-tools__drawer {\n transition: max-width var(--_panel-tools-expand-dur)\n var(--effect-animation-easing-ease-out, ease-out);\n}\n\n:host(.panel-tools--ready.panel-tools--motion-closing) .panel-tools__drawer {\n transition: max-width var(--_panel-tools-expand-dur)\n var(--effect-animation-easing-ease-in, ease-in);\n}\n\n:host(.panel-tools--open) .panel-tools__drawer {\n max-width: var(--_panel-tools-drawer-width);\n}\n\n.panel-tools__drawer--visible {\n border-left: var(--_panel-tools-border);\n}\n\n/* Fixed-width surface — ShellApp supplies the standard 300px tools width. */\n.panel-tools__drawer-surface {\n display: flex;\n flex-direction: column;\n width: var(--_panel-tools-drawer-width);\n height: 100%;\n min-height: 0;\n box-sizing: border-box;\n background-color: transparent;\n}\n\n.panel-tools__full-views,\n.panel-tools__full-view {\n flex: 1 1 auto;\n min-height: 0;\n overflow: hidden;\n}\n\n.panel-tools__full-view {\n height: 100%;\n}\n\n.panel-tools__full-view {\n display: none;\n}\n.panel-tools__full-view--active {\n display: block;\n}\n\n:host([presentation='fullscreen']) {\n height: 100%;\n inset: 0;\n position: fixed;\n width: 100%;\n z-index: var(--dimension-z-index-modal);\n}\n\n:host([presentation='fullscreen']) .panel-tools__rail {\n display: none;\n}\n:host([presentation='fullscreen']) .panel-tools__drawer {\n max-width: 100%;\n transition: none;\n width: 100%;\n}\n:host([presentation='fullscreen']) .panel-tools__drawer--visible {\n border-left: none;\n}\n:host([presentation='fullscreen']) .panel-tools__drawer-surface {\n background-color: transparent;\n width: 100%;\n}\n\n/*\n * A framework can update the reflected presentation attribute before Stencil\n * commits the matching render class. Conceal that single inconsistent frame so\n * slotted master/detail content never paints against the previous geometry.\n */\n:host([presentation='fullscreen']:not(.panel-tools--fullscreen)) .panel-tools__drawer-surface,\n:host(.panel-tools--fullscreen:not([presentation='fullscreen'])) .panel-tools__drawer-surface {\n visibility: hidden;\n}\n\n:host(.panel-tools--presentation-snap) .panel-tools__drawer {\n transition: none;\n}\n\n.panel-tools__body {\n flex: 1 1 auto;\n min-height: 0;\n overflow: auto;\n position: relative;\n}\n\n/* Skip paint while fully closed; cleared during open/close motion for WYSIWYG slide. */\n:host(.panel-tools--drawer-resting) .panel-tools__body {\n content-visibility: auto;\n contain-intrinsic-size: auto 200px;\n}\n\n.panel-tools__view {\n display: none;\n height: 100%;\n min-height: 0;\n box-sizing: border-box;\n padding: var(--dimension-space-200);\n color: var(--_panel-tools-fg-secondary);\n font-family: var(--typography-font-family-ui);\n font-size: var(--typography-fontsize-md);\n line-height: var(--typography-lineheight-md);\n font-weight: var(--typography-weight-regular);\n letter-spacing: var(--typography-letterspacing-negative-half);\n}\n\n.panel-tools__view ::slotted(*) {\n margin: 0;\n color: var(--_panel-tools-fg-secondary);\n font-family: var(--typography-font-family-ui);\n font-size: var(--typography-fontsize-md);\n line-height: var(--typography-lineheight-md);\n font-weight: var(--typography-weight-regular);\n letter-spacing: var(--typography-letterspacing-negative-half);\n}\n\n.panel-tools__view--active {\n display: block;\n}\n\n/* Rail — mirrors collapsed panel-nav width, padding, and item rhythm. */\n.panel-tools__rail {\n flex: 0 0 var(--_panel-tools-rail-width);\n width: var(--_panel-tools-rail-width);\n min-width: var(--_panel-tools-rail-width);\n display: flex;\n flex-direction: column;\n box-sizing: border-box;\n height: 100%;\n min-height: 0;\n border-left: var(--_panel-tools-border);\n background-color: transparent;\n}\n\n.panel-tools__rail-header {\n justify-content: center;\n height: var(--dimension-size-600);\n flex-shrink: 0;\n border-bottom: var(--_panel-tools-divider);\n}\n\n.panel-tools__rail-header.panel-tools__rail-section--stacked {\n height: auto;\n min-height: var(--dimension-size-600);\n}\n\n.panel-tools__rail-body {\n flex: 1 1 auto;\n min-height: 0;\n overflow-y: auto;\n overflow-x: hidden;\n}\n\n.panel-tools__rail-actions {\n display: flex;\n flex-direction: column;\n gap: var(--dimension-space-050);\n width: 100%;\n}\n\n/* Flush to the bottom of the rail — mirrors Search header pin. */\n.panel-tools__rail-footer {\n justify-content: center;\n flex-shrink: 0;\n margin-top: auto;\n}\n\n.panel-tools__rail-section--stacked {\n align-items: stretch;\n}\n\n.panel-tools__rail-action {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 100%;\n height: var(--ds-control-height);\n flex-shrink: 0;\n}\n\n.panel-tools__accessory {\n display: flex;\n flex-direction: column;\n flex: 0 0 calc(var(--dimension-size-400) * 2);\n box-sizing: border-box;\n width: 100%;\n min-height: calc(var(--dimension-size-400) * 2);\n}\n\n.panel-tools__accessory--shortcut {\n flex-basis: var(--dimension-size-400);\n min-width: var(--dimension-size-400);\n min-height: var(--dimension-size-400);\n}\n\n.panel-tools__accessory--shortcut > ds-tooltip {\n display: flex;\n flex: 1 1 auto;\n width: 100%;\n min-height: 0;\n}\n\n.panel-tools__accessory--transient {\n align-items: center;\n gap: var(--dimension-space-100);\n width: var(--dimension-size-400);\n height: calc(var(--dimension-size-400) * 2);\n min-height: calc(var(--dimension-size-400) * 2);\n padding: var(--dimension-space-050);\n border-radius: var(--dimension-radius-half);\n}\n\n.panel-tools__accessory--transient-active {\n background-color: var(--color-background-bold-brand);\n}\n\n.panel-tools__accessory--transient-positive {\n background-color: var(--color-background-bold-positive);\n}\n\n.panel-tools__accessory--transient > ds-tooltip {\n display: flex;\n flex: 0 0 var(--dimension-size-300);\n width: var(--dimension-size-300);\n height: var(--dimension-size-300);\n min-height: var(--dimension-size-300);\n}\n\n.panel-tools__shortcut {\n display: flex;\n align-items: center;\n justify-content: center;\n box-sizing: border-box;\n width: 100%;\n height: 100%;\n margin: 0;\n padding: 0;\n border: 0;\n border-radius: var(--dimension-radius-half);\n color: var(--_panel-tools-shortcut-fg-secondary);\n background: transparent;\n font: inherit;\n}\n\n.panel-tools__shortcut:disabled {\n color: var(--color-foreground-disabled);\n cursor: not-allowed;\n}\n\n.panel-tools__shortcut-content {\n display: flex;\n align-items: center;\n justify-content: center;\n width: var(--dimension-size-400);\n height: var(--dimension-size-400);\n}\n\n.panel-tools__shortcut-orb {\n display: flex;\n align-items: center;\n justify-content: center;\n box-sizing: border-box;\n width: var(--dimension-size-300);\n height: var(--dimension-size-300);\n border: var(--dimension-stroke-width-012) solid var(--_panel-tools-shortcut-border);\n border-radius: var(--dimension-radius-half);\n background: transparent;\n}\n\n.panel-tools__shortcut-dot {\n position: absolute;\n inset-block-start: calc((var(--dimension-size-400) - var(--dimension-iconography-md)) / 2);\n inset-inline-end: calc((var(--dimension-size-400) - var(--dimension-iconography-md)) / 2);\n z-index: 2;\n pointer-events: none;\n}\n\n.panel-tools__accessory-primary {\n display: flex;\n align-items: center;\n justify-content: center;\n box-sizing: border-box;\n width: var(--dimension-size-300);\n height: var(--dimension-size-300);\n margin: 0;\n padding: 0;\n border: 0;\n border-radius: var(--dimension-radius-half);\n color: var(--color-foreground-on-bold-background-secondary);\n background: transparent;\n font: inherit;\n --ds-interaction-border-width: var(--dimension-stroke-width-012);\n --ds-interaction-border-color: var(--color-border-on-bold-background-secondary);\n}\n\n.panel-tools__accessory-primary:disabled {\n color: var(--color-foreground-on-bold-background-quaternary);\n cursor: not-allowed;\n}\n\n.panel-tools__accessory-primary-content {\n display: flex;\n align-items: center;\n justify-content: center;\n position: relative;\n width: 100%;\n height: 100%;\n}\n\n.panel-tools__accessory-image,\n.panel-tools__accessory-initial {\n display: flex;\n align-items: center;\n justify-content: center;\n box-sizing: border-box;\n width: 100%;\n height: 100%;\n border-radius: var(--dimension-radius-half);\n}\n\n.panel-tools__accessory-image {\n object-fit: cover;\n}\n\n.panel-tools__accessory-initial {\n color: inherit;\n background: transparent;\n}\n\n.panel-tools__accessory-secondary {\n display: inline-flex;\n flex: 0 0 var(--dimension-size-300);\n width: var(--dimension-size-300);\n height: var(--dimension-size-300);\n}\n\n.panel-tools__accessory-divider {\n flex: 0 0 auto;\n box-sizing: border-box;\n width: 100%;\n padding-block: var(--dimension-space-050);\n}\n\n.panel-tools__accessory-divider ds-divider {\n display: block;\n --_divider-color: var(--_panel-tools-divider-color);\n}\n\n@media (prefers-reduced-motion: reduce) {\n :host(.panel-tools--ready.panel-tools--motion-opening) .panel-tools__drawer,\n :host(.panel-tools--ready.panel-tools--motion-closing) .panel-tools__drawer {\n transition: none;\n }\n}\n",
545
+ "content": "@import '../../utils/radius.css';\n@import '../../utils/control-density.css';\n@import '../../utils/chrome-layout.css';\n@import '../../utils/focus-ring.css';\n@import '../../utils/interaction-fill.css';\n@import '../../utils/scrollbar-hidden.css';\n\n:host {\n display: block;\n flex: 0 0 auto;\n box-sizing: border-box;\n height: 100%;\n min-height: 0;\n overflow: hidden;\n --_panel-tools-rail-width: var(--dimension-size-600);\n --_panel-tools-drawer-width: var(--ds-shell-panel-tools-width, var(--dimension-panel-width-xs));\n --_panel-tools-border: var(--dimension-stroke-width-012, 1px) solid\n var(--_shell-tools-border, var(--color-border-tertiary));\n --_panel-tools-divider-color: var(--_shell-tools-divider, var(--color-divider-divider));\n --_panel-tools-divider: var(--dimension-stroke-width-012, 1px) solid\n var(--_panel-tools-divider-color);\n --_panel-tools-fg-secondary: var(--_shell-tools-fg-secondary, var(--color-foreground-secondary));\n --_panel-tools-shortcut-fg-secondary: var(\n --_shell-tools-fg-secondary,\n var(--color-chrome-foreground-secondary)\n );\n --_panel-tools-shortcut-border: var(\n --_shell-tools-border-secondary,\n var(--color-chrome-border-secondary)\n );\n --_panel-tools-shortcut-dot-ring: var(\n --_shell-tools-background-theme,\n var(--color-chrome-background-theme)\n );\n /* Match panel-nav expand timing (medium-1); easing differs by direction. */\n --_panel-tools-expand-dur: var(--effect-animation-duration-medium-1, 300ms);\n}\n\n.panel-tools__layout {\n display: flex;\n flex-direction: row-reverse;\n align-items: stretch;\n height: 100%;\n min-height: 0;\n box-sizing: border-box;\n}\n\n/* Clip frame — only max-width animates (slide reveal, same as pre-rail :host). */\n.panel-tools__drawer {\n flex: 0 0 auto;\n max-width: 0;\n min-width: 0;\n height: 100%;\n min-height: 0;\n overflow: hidden;\n box-sizing: border-box;\n transition: none;\n}\n\n:host(.panel-tools--ready) .panel-tools__drawer {\n transition: max-width var(--_panel-tools-expand-dur)\n var(--effect-animation-easing-ease-in-out, ease);\n}\n\n:host(.panel-tools--ready.panel-tools--motion-opening) .panel-tools__drawer {\n transition: max-width var(--_panel-tools-expand-dur)\n var(--effect-animation-easing-ease-out, ease-out);\n}\n\n:host(.panel-tools--ready.panel-tools--motion-closing) .panel-tools__drawer {\n transition: max-width var(--_panel-tools-expand-dur)\n var(--effect-animation-easing-ease-in, ease-in);\n}\n\n:host(.panel-tools--open) .panel-tools__drawer {\n max-width: var(--_panel-tools-drawer-width);\n}\n\n.panel-tools__drawer--visible {\n border-left: var(--_panel-tools-border);\n}\n\n/* Fixed-width surface — ShellApp supplies the standard 300px tools width. */\n.panel-tools__drawer-surface {\n display: flex;\n flex-direction: column;\n width: var(--_panel-tools-drawer-width);\n height: 100%;\n min-height: 0;\n box-sizing: border-box;\n background-color: transparent;\n}\n\n.panel-tools__full-views,\n.panel-tools__full-view {\n flex: 1 1 auto;\n min-height: 0;\n overflow: hidden;\n}\n\n.panel-tools__full-view {\n height: 100%;\n}\n\n.panel-tools__full-view {\n display: none;\n}\n.panel-tools__full-view--active {\n display: block;\n}\n\n:host([presentation='fullscreen']) {\n height: 100%;\n inset: 0;\n position: fixed;\n width: 100%;\n z-index: var(--dimension-z-index-modal);\n}\n\n:host([presentation='fullscreen']) .panel-tools__rail {\n display: none;\n}\n:host([presentation='fullscreen']) .panel-tools__drawer {\n max-width: 100%;\n transition: none;\n width: 100%;\n}\n:host([presentation='fullscreen']) .panel-tools__drawer--visible {\n border-left: none;\n}\n:host([presentation='fullscreen']) .panel-tools__drawer-surface {\n background-color: transparent;\n width: 100%;\n}\n\n/*\n * A framework can update the reflected presentation attribute before Stencil\n * commits the matching render class. Conceal that single inconsistent frame so\n * slotted master/detail content never paints against the previous geometry.\n */\n:host([presentation='fullscreen']:not(.panel-tools--fullscreen)) .panel-tools__drawer-surface,\n:host(.panel-tools--fullscreen:not([presentation='fullscreen'])) .panel-tools__drawer-surface {\n visibility: hidden;\n}\n\n:host(.panel-tools--presentation-snap) .panel-tools__drawer {\n transition: none;\n}\n\n.panel-tools__body {\n flex: 1 1 auto;\n min-height: 0;\n overflow: auto;\n position: relative;\n}\n\n/* Skip paint while fully closed; cleared during open/close motion for WYSIWYG slide. */\n:host(.panel-tools--drawer-resting) .panel-tools__body {\n content-visibility: auto;\n contain-intrinsic-size: auto calc(var(--dimension-size-base) * 25);\n}\n\n.panel-tools__view {\n display: none;\n height: 100%;\n min-height: 0;\n box-sizing: border-box;\n padding: var(--dimension-space-200);\n color: var(--_panel-tools-fg-secondary);\n font-family: var(--typography-font-family-ui);\n font-size: var(--typography-fontsize-md);\n line-height: var(--typography-lineheight-md);\n font-weight: var(--typography-weight-regular);\n letter-spacing: var(--typography-letterspacing-negative-half);\n}\n\n.panel-tools__view ::slotted(*) {\n margin: 0;\n color: var(--_panel-tools-fg-secondary);\n font-family: var(--typography-font-family-ui);\n font-size: var(--typography-fontsize-md);\n line-height: var(--typography-lineheight-md);\n font-weight: var(--typography-weight-regular);\n letter-spacing: var(--typography-letterspacing-negative-half);\n}\n\n.panel-tools__view--active {\n display: block;\n}\n\n/* Rail — mirrors collapsed panel-nav width, padding, and item rhythm. */\n.panel-tools__rail {\n flex: 0 0 var(--_panel-tools-rail-width);\n width: var(--_panel-tools-rail-width);\n min-width: var(--_panel-tools-rail-width);\n display: flex;\n flex-direction: column;\n box-sizing: border-box;\n height: 100%;\n min-height: 0;\n border-left: var(--_panel-tools-border);\n background-color: transparent;\n}\n\n.panel-tools__rail-header {\n justify-content: center;\n height: var(--dimension-size-600);\n flex-shrink: 0;\n border-bottom: var(--_panel-tools-divider);\n}\n\n.panel-tools__rail-header.panel-tools__rail-section--stacked {\n height: auto;\n min-height: var(--dimension-size-600);\n}\n\n.panel-tools__rail-body {\n flex: 1 1 auto;\n min-height: 0;\n overflow-y: auto;\n overflow-x: hidden;\n}\n\n.panel-tools__rail-actions {\n display: flex;\n flex-direction: column;\n gap: var(--dimension-space-050);\n width: 100%;\n}\n\n/* Flush to the bottom of the rail — mirrors Search header pin. */\n.panel-tools__rail-footer {\n justify-content: center;\n flex-shrink: 0;\n margin-top: auto;\n}\n\n.panel-tools__rail-section--stacked {\n align-items: stretch;\n}\n\n.panel-tools__rail-action {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 100%;\n height: var(--ds-control-height);\n flex-shrink: 0;\n}\n\n.panel-tools__accessory {\n display: flex;\n flex-direction: column;\n flex: 0 0 calc(var(--dimension-size-400) * 2);\n box-sizing: border-box;\n width: 100%;\n min-height: calc(var(--dimension-size-400) * 2);\n}\n\n.panel-tools__accessory--shortcut {\n flex-basis: var(--dimension-size-400);\n min-width: var(--dimension-size-400);\n min-height: var(--dimension-size-400);\n}\n\n.panel-tools__accessory--shortcut > ds-tooltip {\n display: flex;\n flex: 1 1 auto;\n width: 100%;\n min-height: 0;\n}\n\n.panel-tools__accessory--transient {\n align-items: center;\n gap: var(--dimension-space-100);\n width: var(--dimension-size-400);\n height: calc(var(--dimension-size-400) * 2);\n min-height: calc(var(--dimension-size-400) * 2);\n padding: var(--dimension-space-050);\n border-radius: var(--dimension-radius-half);\n}\n\n.panel-tools__accessory--transient-active {\n background-color: var(--color-background-bold-brand);\n}\n\n.panel-tools__accessory--transient-positive {\n background-color: var(--color-background-bold-positive);\n}\n\n.panel-tools__accessory--transient > ds-tooltip {\n display: flex;\n flex: 0 0 var(--dimension-size-300);\n width: var(--dimension-size-300);\n height: var(--dimension-size-300);\n min-height: var(--dimension-size-300);\n}\n\n.panel-tools__shortcut {\n display: flex;\n align-items: center;\n justify-content: center;\n box-sizing: border-box;\n width: 100%;\n height: 100%;\n margin: 0;\n padding: 0;\n border: 0;\n border-radius: var(--dimension-radius-half);\n color: var(--_panel-tools-shortcut-fg-secondary);\n background: transparent;\n font: inherit;\n}\n\n.panel-tools__shortcut:disabled {\n color: var(--color-foreground-disabled);\n cursor: not-allowed;\n}\n\n.panel-tools__shortcut-content {\n display: flex;\n align-items: center;\n justify-content: center;\n width: var(--dimension-size-400);\n height: var(--dimension-size-400);\n}\n\n.panel-tools__shortcut-orb {\n display: flex;\n align-items: center;\n justify-content: center;\n box-sizing: border-box;\n width: var(--dimension-size-300);\n height: var(--dimension-size-300);\n border: var(--dimension-stroke-width-012) solid var(--_panel-tools-shortcut-border);\n border-radius: var(--dimension-radius-half);\n background: transparent;\n}\n\n.panel-tools__shortcut-dot {\n position: absolute;\n inset-block-start: calc((var(--dimension-size-400) - var(--dimension-iconography-md)) / 2);\n inset-inline-end: calc((var(--dimension-size-400) - var(--dimension-iconography-md)) / 2);\n z-index: 2;\n pointer-events: none;\n}\n\n.panel-tools__accessory-primary {\n display: flex;\n align-items: center;\n justify-content: center;\n box-sizing: border-box;\n width: var(--dimension-size-300);\n height: var(--dimension-size-300);\n margin: 0;\n padding: 0;\n border: 0;\n border-radius: var(--dimension-radius-half);\n color: var(--color-foreground-on-bold-background-secondary);\n background: transparent;\n font: inherit;\n --ds-interaction-border-width: var(--dimension-stroke-width-012);\n --ds-interaction-border-color: var(--color-border-on-bold-background-secondary);\n}\n\n.panel-tools__accessory-primary:disabled {\n color: var(--color-foreground-on-bold-background-quaternary);\n cursor: not-allowed;\n}\n\n.panel-tools__accessory-primary-content {\n display: flex;\n align-items: center;\n justify-content: center;\n position: relative;\n width: 100%;\n height: 100%;\n}\n\n.panel-tools__accessory-image,\n.panel-tools__accessory-initial {\n display: flex;\n align-items: center;\n justify-content: center;\n box-sizing: border-box;\n width: 100%;\n height: 100%;\n border-radius: var(--dimension-radius-half);\n}\n\n.panel-tools__accessory-image {\n object-fit: cover;\n}\n\n.panel-tools__accessory-initial {\n color: inherit;\n background: transparent;\n}\n\n.panel-tools__accessory-secondary {\n display: inline-flex;\n flex: 0 0 var(--dimension-size-300);\n width: var(--dimension-size-300);\n height: var(--dimension-size-300);\n}\n\n.panel-tools__accessory-divider {\n flex: 0 0 auto;\n box-sizing: border-box;\n width: 100%;\n padding-block: var(--dimension-space-050);\n}\n\n.panel-tools__accessory-divider ds-divider {\n display: block;\n --_divider-color: var(--_panel-tools-divider-color);\n}\n\n@media (prefers-reduced-motion: reduce) {\n :host(.panel-tools--ready.panel-tools--motion-opening) .panel-tools__drawer,\n :host(.panel-tools--ready.panel-tools--motion-closing) .panel-tools__drawer {\n transition: none;\n }\n}\n",
546
546
  "type": "registry:ui"
547
547
  },
548
548
  {
@@ -4308,7 +4308,7 @@
4308
4308
  ],
4309
4309
  "responsiveBehavior": [
4310
4310
  "The chart observes its container unless width is fixed and recompiles geometry at CSS pixel dimensions.",
4311
- "Height uses an authored height or aspectRatio, fills the chart region supplied by CardChart, and otherwise defaults to 320px.",
4311
+ "Height uses an authored height or aspectRatio, fills the chart region supplied by CardChart, and otherwise derives its default height from the shared size scale.",
4312
4312
  "Measured guide labels reserve margins and thin colliding ticks while retaining boundary ticks."
4313
4313
  ],
4314
4314
  "frameworkCaveats": {
@@ -5096,6 +5096,7 @@
5096
5096
  "component:ds-button-unfilled"
5097
5097
  ],
5098
5098
  "accessibility": [
5099
+ "Overflowing code is a named keyboard scroll region with visible focus. Code that fits does not add a tab stop; focusability follows content, font loading, and container resizing.",
5099
5100
  "The copy action has matching accessible-name and visible-tooltip status wording, and the code remains selectable text.",
5100
5101
  "Language and filename labels supplement rather than replace the code content.",
5101
5102
  "The semantic code-family token retains a system-monospace fallback when an application does not load Fira Code; applications may disable programming ligatures through the shared code-surface custom property."
@@ -10359,7 +10360,7 @@
10359
10360
  ],
10360
10361
  "responsiveBehavior": [
10361
10362
  "Render only below the mobile breakpoint; its primary surface extends through the bottom safe-area inset when the application enables viewport-fit=cover.",
10362
- "The primary-background bar keeps an 8px outer inset; its two icon-only groups anchor to opposite edges while retaining fixed 40px targets and a divider between Menu and current area.",
10363
+ "The primary-background bar uses shared chrome spacing and medium control recipes; the responsive profile enlarges both consistently. Its two icon-only groups anchor to opposite edges with a divider between Menu and current area.",
10363
10364
  "Bar chrome and its divider are not text-selectable and suppress the iOS touch callout."
10364
10365
  ],
10365
10366
  "references": [
@@ -10538,7 +10539,7 @@
10538
10539
  "Brand tone changes surface and foreground without changing geometry or interaction ownership."
10539
10540
  ],
10540
10541
  "responsiveBehavior": [
10541
- "The primary 56px lane matches the Mobile Sheet Nav header and Mobile Bar Nav; optional subsections add one subordinate 48px lane.",
10542
+ "The primary lane, subordinate lane, Mobile Sheet Nav header, and Mobile Bar Nav share a medium baseline; the global responsive profile supplies their mobile enlargement once.",
10542
10543
  "Equal flexible side lanes keep the center mathematically aligned when actions are asymmetric; segmented headers allocate the usable lane in a 1:4:1 ratio."
10543
10544
  ],
10544
10545
  "references": [
@@ -10696,6 +10697,7 @@
10696
10697
  ],
10697
10698
  "responsiveBehavior": [
10698
10699
  "Use only in the dedicated mobile header presentation.",
10700
+ "Triggers, sheet rows, and labels use the medium baseline so the global responsive profile applies mobile enlargement once.",
10699
10701
  "While a section sheet is open or closing, an opaque bottom-edge surface matches the shade composited over the primary mobile bar background, allowing browsers that sample fixed edges to tint their controls independently of the top sheet. Browser tint remains best-effort and requires native-device verification."
10700
10702
  ],
10701
10703
  "references": [
@@ -10782,7 +10784,7 @@
10782
10784
  {
10783
10785
  "name": "mobile-sheet-nav",
10784
10786
  "title": "MobileSheetNav",
10785
- "description": "Full-height mobile navigation sheet with a single brand, context, and utility header above large primary destinations.",
10787
+ "description": "Full-height mobile navigation sheet with a single brand, context, and utility header above primary destinations.",
10786
10788
  "type": "registry:ui",
10787
10789
  "meta": {
10788
10790
  "library": "@ds-mo/ui",
@@ -10793,7 +10795,7 @@
10793
10795
  "intent": {
10794
10796
  "audience": "specialized",
10795
10797
  "status": "experimental",
10796
- "summary": "Full-height mobile navigation sheet with a single brand, context, and utility header above large primary destinations.",
10798
+ "summary": "Full-height mobile navigation sheet with a single brand, context, and utility header above primary destinations.",
10797
10799
  "useWhen": [
10798
10800
  "The mobile ShellApp Menu control needs to expose the complete primary navigation collection."
10799
10801
  ],
@@ -10824,9 +10826,9 @@
10824
10826
  ],
10825
10827
  "responsiveBehavior": [
10826
10828
  "The pane fills the mobile content stage while the bottom bar remains available.",
10827
- "The sheet header and scrollable navigation body each use the shared 8px chrome inset; the context TabGroup and primary controls share a 40px height and 24px icon scale.",
10829
+ "The sheet header and scrollable navigation body use shared chrome spacing; the context TabGroup, primary controls, icons, and text share the medium baseline and receive mobile enlargement from the global responsive profile.",
10828
10830
  "When the optional Account shortcut is hidden, the Dashboard and Settings TabGroup fills the centered two-thirds top-bar lane and gives both context options equal width.",
10829
- "Dashboard and Settings preserve their supplied visual groups with an 8px gap between destinations inside a section and a 32px gap between sections."
10831
+ "Dashboard and Settings preserve their supplied visual groups with token-backed destination and section gaps."
10830
10832
  ],
10831
10833
  "references": [
10832
10834
  {
@@ -14327,7 +14329,7 @@
14327
14329
  },
14328
14330
  {
14329
14331
  "name": "dsHeaderAction",
14330
- "detail": "{ tool: PanelToolsToolId; id: string }"
14332
+ "detail": "PanelToolsHeaderActionDetail"
14331
14333
  },
14332
14334
  {
14333
14335
  "name": "dsHeaderBack",
@@ -14643,7 +14645,7 @@
14643
14645
  ],
14644
14646
  "responsiveBehavior": [
14645
14647
  "Desktop and tablet compose PanelTools-compatible rail and drawer chrome.",
14646
- "Mobile renders one centered MobileHeader for every direct tool; explicitly grouped Inbox tools use the large segmented section control.",
14648
+ "Mobile renders one centered MobileHeader for every direct tool; explicitly grouped Inbox tools use the medium segmented section control and share the global responsive scale.",
14647
14649
  "Mobile intentionally omits desktop and tablet rail accessories instead of promoting them into tool views. Applications provide an equivalent persistent mobile status surface through ShellApp's top banner or status region."
14648
14650
  ],
14649
14651
  "frameworkCaveats": {
@@ -14753,7 +14755,7 @@
14753
14755
  "events": [
14754
14756
  {
14755
14757
  "name": "dsHeaderAction",
14756
- "detail": "{\n tool: PanelToolsToolId;\n id: string;\n }"
14758
+ "detail": "PanelToolsHeaderActionDetail"
14757
14759
  },
14758
14760
  {
14759
14761
  "name": "dsHeaderBack",